Skip to content

Commit

Permalink
Merge pull request #77 from petretiandrea/dev
Browse files Browse the repository at this point in the history
release 1.2.0
  • Loading branch information
petretiandrea authored Oct 1, 2021
2 parents c0a7bff + 7d6901b commit 00ee416
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 87 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ logger:
logs:
custom_components.tapo: debug
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
# debugpy:
# debugpy:
72 changes: 13 additions & 59 deletions custom_components/tapo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
"""The tapo integration."""
import logging
import asyncio
import async_timeout
from datetime import timedelta

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import UpdateFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.debounce import Debouncer

from .const import DOMAIN, PLATFORMS, CONF_HOST, CONF_USERNAME, CONF_PASSWORD

from plugp100 import TapoApiClient, TapoDeviceState
from custom_components.tapo.common_setup import (
setup_tapo_coordinator_from_config_entry,
)
from .const import DOMAIN, PLATFORMS

_LOGGGER = logging.getLogger(__name__)

Expand All @@ -27,29 +21,17 @@ async def async_setup(hass: HomeAssistant, config: dict):

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up tapo from a config entry."""

host = entry.data.get(CONF_HOST)
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)

session = async_get_clientsession(hass)
client = TapoApiClient(host, username, password, session)

coordinator = TapoUpdateCoordinator(hass, client=client)
await coordinator.async_refresh()

if not coordinator.last_update_success:
try:
coordinator = await setup_tapo_coordinator_from_config_entry(hass, entry)
hass.data[DOMAIN][entry.entry_id] = coordinator
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
return True
except:
raise ConfigEntryNotReady

hass.data[DOMAIN][entry.entry_id] = coordinator

for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
Expand All @@ -65,31 +47,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok

SCAN_INTERVAL = timedelta(seconds=30)
DEBOUNCER_COOLDOWN = 2

class TapoUpdateCoordinator(DataUpdateCoordinator[TapoDeviceState]):
def __init__(self, hass: HomeAssistant, client: TapoApiClient):
self.api = client
debouncer = Debouncer(hass, _LOGGGER, cooldown=DEBOUNCER_COOLDOWN, immediate=True)
super().__init__(hass, _LOGGGER, name=DOMAIN, update_interval=SCAN_INTERVAL, request_refresh_debouncer=debouncer)

@property
def tapo_client(self) -> TapoApiClient:
return self.api

async def _async_update_data(self):
try:
async with async_timeout.timeout(10):
return await self._update_with_fallback()
except Exception as exception:
raise UpdateFailed() from exception

async def _update_with_fallback(self, retry=True):
try:
return await self.api.get_state()
except Exception as error:
if retry:
await self.api.login()
return await self._update_with_fallback(False)
100 changes: 100 additions & 0 deletions custom_components/tapo/common_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import async_timeout
import logging
from typing import Dict, Any
from datetime import timedelta
from dataclasses import dataclass
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from plugp100 import TapoApiClient, TapoDeviceState
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import UpdateFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.debounce import Debouncer
from custom_components.tapo.const import (
DOMAIN,
PLATFORMS,
CONF_HOST,
CONF_USERNAME,
CONF_PASSWORD,
)


_LOGGGER = logging.getLogger(__name__)


async def setup_tapo_coordinator_from_dictionary(
hass: HomeAssistant, entry: Dict[str, Any]
) -> "TapoUpdateCoordinator":
return await setup_tapo_coordinator(
hass,
entry.get(CONF_HOST),
entry.get(CONF_USERNAME),
entry.get(CONF_PASSWORD),
)


async def setup_tapo_coordinator_from_config_entry(
hass: HomeAssistant, entry: ConfigEntry
) -> "TapoUpdateCoordinator":
return await setup_tapo_coordinator(
hass,
entry.data.get(CONF_HOST),
entry.data.get(CONF_USERNAME),
entry.data.get(CONF_PASSWORD),
)


async def setup_tapo_coordinator(
hass: HomeAssistant, host: str, username: str, password: str
) -> "TapoUpdateCoordinator":
session = async_get_clientsession(hass)
client = TapoApiClient(host, username, password, session)

coordinator = TapoUpdateCoordinator(hass, client=client)
await coordinator.async_refresh()

if not coordinator.last_update_success:
raise Exception("Failed to retrieve first tapo data")

return coordinator


SCAN_INTERVAL = timedelta(seconds=30)
DEBOUNCER_COOLDOWN = 2


class TapoUpdateCoordinator(DataUpdateCoordinator[TapoDeviceState]):
def __init__(self, hass: HomeAssistant, client: TapoApiClient):
self.api = client
debouncer = Debouncer(
hass, _LOGGGER, cooldown=DEBOUNCER_COOLDOWN, immediate=True
)
super().__init__(
hass,
_LOGGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
request_refresh_debouncer=debouncer,
)

@property
def tapo_client(self) -> TapoApiClient:
return self.api

async def _async_update_data(self):
try:
async with async_timeout.timeout(10):
return await self._update_with_fallback()
except Exception as exception:
raise UpdateFailed() from exception

async def _update_with_fallback(self, retry=True):
try:
return await self.api.get_state()
except Exception as error:
if retry:
await self.api.login()
return await self._update_with_fallback(False)
2 changes: 1 addition & 1 deletion custom_components/tapo/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from plugp100 import TapoApiClient
from plugp100.core.exceptions.TapoException import TapoException

from .const import (
from custom_components.tapo.const import (
DOMAIN,
CONF_HOST,
CONF_USERNAME,
Expand Down
3 changes: 2 additions & 1 deletion custom_components/tapo/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
VERSION = "1.0"

SUPPORTED_DEVICE_AS_SWITCH = ["p100", "p105", "p110"]
SUPPORTED_DEVICE_AS_SWITCH_POWER_MONITOR = ["p110"]
SUPPORTED_DEVICE_AS_LIGHT = {
"l530": SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR,
"l510": SUPPORT_BRIGHTNESS,
Expand All @@ -20,7 +21,7 @@

# list the platforms that you want to support.
# TODO: add suport for ligth and use "model" from get_state of tapo
PLATFORMS = ["switch", "light"]
PLATFORMS = ["switch", "light", "sensor"]

CONF_HOST = "host"
CONF_USERNAME = "username"
Expand Down
39 changes: 27 additions & 12 deletions custom_components/tapo/light.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import Dict, Any, Callable
from custom_components.tapo.common_setup import (
TapoUpdateCoordinator,
setup_tapo_coordinator_from_dictionary,
)
from custom_components.tapo.utils import clamp
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.components.light import (
LightEntity,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
ATTR_BRIGHTNESS,
Expand All @@ -14,31 +18,39 @@
color_temperature_kelvin_to_mired as kelvin_to_mired,
color_temperature_mired_to_kelvin as mired_to_kelvin,
)
from typing import List, Optional
from plugp100 import TapoDeviceState

from . import TapoUpdateCoordinator
from .tapo_entity import TapoEntity
from .const import DOMAIN, SUPPORTED_DEVICE_AS_LIGHT
from custom_components.tapo.tapo_entity import TapoEntity
from custom_components.tapo.const import DOMAIN, SUPPORTED_DEVICE_AS_LIGHT


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_devices):
# get tapo helper
coordinator: TapoUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
_setup_from_coordinator(coordinator, async_add_devices)


async def async_setup_platform(
hass: HomeAssistant,
config: Dict[str, Any],
async_add_entities: Callable,
discovery_info=None,
) -> None:
coordinator = await setup_tapo_coordinator_from_dictionary(hass, config)
_setup_from_coordinator(coordinator, async_add_entities)


def _setup_from_coordinator(coordinator: TapoUpdateCoordinator, async_add_devices):
for (model, capabilities) in SUPPORTED_DEVICE_AS_LIGHT.items():
if model.lower() in coordinator.data.model.lower():
light = TapoLight(
coordinator,
entry,
capabilities,
)
async_add_devices([light], True)


class TapoLight(TapoEntity, LightEntity):
def __init__(self, coordinator, config_entry, features: int):
super().__init__(coordinator, config_entry)
def __init__(self, coordinator, features: int):
super().__init__(coordinator)
self.features = features
self._max_kelvin = 6500
self._min_kelvin = 2500
Expand All @@ -53,7 +65,6 @@ def is_on(self):
def supported_features(self):
"""Flag supported features."""
return self.features
# return SUPPORT_BRIGHTNESS # TODO: return supported feature starting from model type

@property
def brightness(self):
Expand Down Expand Up @@ -114,7 +125,11 @@ async def _set_brightness():

async def _change_color_temp(self, color_temp):
constraint_color_temp = clamp(color_temp, self._min_merids, self._max_merids)
kelvin_color_temp = clamp(mired_to_kelvin(constraint_color_temp), min_value=self._min_kelvin, max_value=self._max_kelvin)
kelvin_color_temp = clamp(
mired_to_kelvin(constraint_color_temp),
min_value=self._min_kelvin,
max_value=self._max_kelvin,
)
await self._execute_with_fallback(
lambda: self._tapo_coordinator.api.set_color_temperature(kelvin_color_temp)
)
Expand Down
4 changes: 2 additions & 2 deletions custom_components/tapo/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
"domain": "tapo",
"name": "Tapo Controller",
"config_flow": true,
"version": "1.1.2",
"version": "1.2.0",
"iot_class": "local_polling",
"documentation": "https://github.com/petretiandrea/home-assistant-tapo-p100",
"issue_tracker": "https://github.com/petretiandrea/home-assistant-tapo-p100/issues/",
"requirements": [
"plugp100==2.1.9"
"plugp100==2.1.10b1"
],
"dependencies": [],
"codeowners": [
Expand Down
24 changes: 24 additions & 0 deletions custom_components/tapo/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from custom_components.tapo.common_setup import TapoUpdateCoordinator
from custom_components.tapo.tapo_sensor_entity import (
TapoCurrentEnergySensor,
TapoTodayEnergySensor,
)
from custom_components.tapo.const import (
DOMAIN,
SUPPORTED_DEVICE_AS_SWITCH_POWER_MONITOR,
)

### Supported sensors: Today energy and current energy
SUPPORTED_SENSOR = [TapoTodayEnergySensor, TapoCurrentEnergySensor]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_devices):
# get tapo helper
coordinator: TapoUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

if coordinator.data.model.lower() in SUPPORTED_DEVICE_AS_SWITCH_POWER_MONITOR:
sensors = [factory(coordinator) for factory in SUPPORTED_SENSOR]
async_add_devices(sensors, True)
30 changes: 24 additions & 6 deletions custom_components/tapo/switch.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
from typing import Callable, Dict, Any
from custom_components.tapo.tapo_entity import TapoEntity
from custom_components.tapo.const import (
DOMAIN,
SUPPORTED_DEVICE_AS_SWITCH,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.components.switch import SwitchEntity
from plugp100 import TapoDeviceState

from . import TapoUpdateCoordinator
from .tapo_entity import TapoEntity
from .const import DOMAIN, SUPPORTED_DEVICE_AS_SWITCH
from custom_components.tapo.common_setup import (
TapoUpdateCoordinator,
setup_tapo_coordinator_from_dictionary,
)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_devices):
# get tapo helper
coordinator: TapoUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
_setup_from_coordinator(coordinator, async_add_devices)


async def async_setup_platform(
hass: HomeAssistant,
config: Dict[str, Any],
async_add_entities: Callable,
discovery_info=None,
) -> None:
coordinator = await setup_tapo_coordinator_from_dictionary(hass, config)
_setup_from_coordinator(coordinator, async_add_entities)


def _setup_from_coordinator(coordinator: TapoUpdateCoordinator, async_add_devices):
if coordinator.data.model.lower() in SUPPORTED_DEVICE_AS_SWITCH:
switch = P100Switch(coordinator, entry)
switch = P100Switch(coordinator)
async_add_devices([switch], True)


Expand Down
Loading

0 comments on commit 00ee416

Please sign in to comment.