-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24636 from home-assistant/rc
0.94.4
- Loading branch information
Showing
11 changed files
with
578 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
"""Common code for tplink.""" | ||
import asyncio | ||
import logging | ||
from datetime import timedelta | ||
from typing import Any, Callable, List | ||
|
||
from pyHS100 import ( | ||
SmartBulb, | ||
SmartDevice, | ||
SmartPlug, | ||
SmartDeviceException | ||
) | ||
|
||
from homeassistant.helpers.typing import HomeAssistantType | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
ATTR_CONFIG = 'config' | ||
CONF_DIMMER = 'dimmer' | ||
CONF_DISCOVERY = 'discovery' | ||
CONF_LIGHT = 'light' | ||
CONF_SWITCH = 'switch' | ||
|
||
|
||
class SmartDevices: | ||
"""Hold different kinds of devices.""" | ||
|
||
def __init__( | ||
self, | ||
lights: List[SmartDevice] = None, | ||
switches: List[SmartDevice] = None | ||
): | ||
"""Constructor.""" | ||
self._lights = lights or [] | ||
self._switches = switches or [] | ||
|
||
@property | ||
def lights(self): | ||
"""Get the lights.""" | ||
return self._lights | ||
|
||
@property | ||
def switches(self): | ||
"""Get the switches.""" | ||
return self._switches | ||
|
||
def has_device_with_host(self, host): | ||
"""Check if a devices exists with a specific host.""" | ||
for device in self.lights + self.switches: | ||
if device.host == host: | ||
return True | ||
|
||
return False | ||
|
||
|
||
async def async_get_discoverable_devices(hass): | ||
"""Return if there are devices that can be discovered.""" | ||
from pyHS100 import Discover | ||
|
||
def discover(): | ||
devs = Discover.discover() | ||
return devs | ||
return await hass.async_add_executor_job(discover) | ||
|
||
|
||
async def async_discover_devices( | ||
hass: HomeAssistantType, | ||
existing_devices: SmartDevices | ||
) -> SmartDevices: | ||
"""Get devices through discovery.""" | ||
_LOGGER.debug("Discovering devices") | ||
devices = await async_get_discoverable_devices(hass) | ||
_LOGGER.info( | ||
"Discovered %s TP-Link smart home device(s)", | ||
len(devices) | ||
) | ||
|
||
lights = [] | ||
switches = [] | ||
|
||
def process_devices(): | ||
for dev in devices.values(): | ||
# If this device already exists, ignore dynamic setup. | ||
if existing_devices.has_device_with_host(dev.host): | ||
continue | ||
|
||
if isinstance(dev, SmartPlug): | ||
try: | ||
if dev.is_dimmable: # Dimmers act as lights | ||
lights.append(dev) | ||
else: | ||
switches.append(dev) | ||
except SmartDeviceException as ex: | ||
_LOGGER.error("Unable to connect to device %s: %s", | ||
dev.host, ex) | ||
|
||
elif isinstance(dev, SmartBulb): | ||
lights.append(dev) | ||
else: | ||
_LOGGER.error("Unknown smart device type: %s", type(dev)) | ||
|
||
await hass.async_add_executor_job(process_devices) | ||
|
||
return SmartDevices(lights, switches) | ||
|
||
|
||
def get_static_devices(config_data) -> SmartDevices: | ||
"""Get statically defined devices in the config.""" | ||
_LOGGER.debug("Getting static devices") | ||
lights = [] | ||
switches = [] | ||
|
||
for type_ in [CONF_LIGHT, CONF_SWITCH, CONF_DIMMER]: | ||
for entry in config_data[type_]: | ||
host = entry['host'] | ||
|
||
if type_ == CONF_LIGHT: | ||
lights.append(SmartBulb(host)) | ||
elif type_ == CONF_SWITCH: | ||
switches.append(SmartPlug(host)) | ||
# Dimmers need to be defined as smart plugs to work correctly. | ||
elif type_ == CONF_DIMMER: | ||
lights.append(SmartPlug(host)) | ||
|
||
return SmartDevices( | ||
lights, | ||
switches | ||
) | ||
|
||
|
||
async def async_add_entities_retry( | ||
hass: HomeAssistantType, | ||
async_add_entities: Callable[[List[Any], bool], None], | ||
objects: List[Any], | ||
callback: Callable[[Any, Callable], None], | ||
interval: timedelta = timedelta(seconds=60) | ||
): | ||
""" | ||
Add entities now and retry later if issues are encountered. | ||
If the callback throws an exception or returns false, that | ||
object will try again a while later. | ||
This is useful for devices that are not online when hass starts. | ||
:param hass: | ||
:param async_add_entities: The callback provided to a | ||
platform's async_setup. | ||
:param objects: The objects to create as entities. | ||
:param callback: The callback that will perform the add. | ||
:param interval: THe time between attempts to add. | ||
:return: A callback to cancel the retries. | ||
""" | ||
add_objects = objects.copy() | ||
|
||
is_cancelled = False | ||
|
||
def cancel_interval_callback(): | ||
nonlocal is_cancelled | ||
is_cancelled = True | ||
|
||
async def process_objects_loop(delay: int): | ||
if is_cancelled: | ||
return | ||
|
||
await process_objects() | ||
|
||
if not add_objects: | ||
return | ||
|
||
await asyncio.sleep(delay) | ||
|
||
hass.async_create_task(process_objects_loop(delay)) | ||
|
||
async def process_objects(*args): | ||
# Process each object. | ||
for add_object in list(add_objects): | ||
# Call the individual item callback. | ||
try: | ||
_LOGGER.debug( | ||
"Attempting to add object of type %s", | ||
type(add_object) | ||
) | ||
result = await hass.async_add_job( | ||
callback, | ||
add_object, | ||
async_add_entities | ||
) | ||
except SmartDeviceException as ex: | ||
_LOGGER.debug( | ||
str(ex) | ||
) | ||
result = False | ||
|
||
if result is True or result is None: | ||
_LOGGER.debug("Added object.") | ||
add_objects.remove(add_object) | ||
else: | ||
_LOGGER.debug("Failed to add object, will try again later") | ||
|
||
await process_objects_loop(interval.seconds) | ||
|
||
return cancel_interval_callback |
Oops, something went wrong.