Add Plum Lightpad config flow (#36802)

* add support for config flow for Plum Lightpad integration

* add support for config flow for Plum Lightpad integration (remove unintended change to requirements_test_all.txt)

* add support for config flow for Plum Lightpad integration (fix lint issues)

* add support for config flow for Plum Lightpad integration (PR feedback)

* add support for config flow for Plum Lightpad integration (fix lint)

* Update homeassistant/components/plum_lightpad/__init__.py

use debug instead of info for logging

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Update homeassistant/components/plum_lightpad/strings.json

switch to use generated references instead of hard-coded strings

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Update homeassistant/components/plum_lightpad/strings.json

switch to use references instead of hard-coded string

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Update homeassistant/components/plum_lightpad/strings.json

removing translated title per suggestion

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Update homeassistant/components/plum_lightpad/strings.json

removing per suggestion

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* remove unnecessary deepcopy

* remove unnecessary logging warning, since ignoring is expected for configuration.yaml scenario

* switch to hass.loop.create_task per PR feedback

* show login errors when configuring integration via UI (PR feedback)

* disable wrongly flag pylint violation

* add except handler to handle connection errors when setting up config flow entry

* address PR feedback regarding exception handling

* Update homeassistant/components/plum_lightpad/config_flow.py

use helper instead of custom code/message-id

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/37050/head
Eugene Prystupa 2020-06-23 23:40:11 -04:00 committed by GitHub
parent 6c7355785a
commit e3b90ea3f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 232 additions and 62 deletions

View File

@ -316,7 +316,7 @@ homeassistant/components/plaato/* @JohNan
homeassistant/components/plant/* @ChristianKuehnel
homeassistant/components/plex/* @jjlawren
homeassistant/components/plugwise/* @CoMPaTech @bouwew
homeassistant/components/plum_lightpad/* @ColinHarrington
homeassistant/components/plum_lightpad/* @ColinHarrington @prystupa
homeassistant/components/point/* @fredrike
homeassistant/components/powerwall/* @bdraco @jrester
homeassistant/components/prometheus/* @knyar

View File

@ -1,16 +1,18 @@
"""Support for Plum Lightpad devices."""
import asyncio
import logging
from plumlightpad import Plum
from aiohttp import ContentTypeError
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from .const import DOMAIN
from .utils import load_plum
_LOGGER = logging.getLogger(__name__)
@ -26,56 +28,53 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
PLATFORMS = ["light"]
async def async_setup(hass, config):
async def async_setup(hass: HomeAssistant, config: dict):
"""Plum Lightpad Platform initialization."""
if DOMAIN not in config:
return True
conf = config[DOMAIN]
plum = Plum(conf[CONF_USERNAME], conf[CONF_PASSWORD])
hass.data[DOMAIN] = plum
_LOGGER.info("Found Plum Lightpad configuration in config, importing...")
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
)
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Plum Lightpad from a config entry."""
_LOGGER.debug("Setting up config entry with ID = %s", entry.unique_id)
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)
try:
plum = await load_plum(username, password, hass)
except ContentTypeError as ex:
_LOGGER.error("Unable to authenticate to Plum cloud: %s", ex)
return False
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Plum cloud: %s", ex)
raise ConfigEntryNotReady
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = plum
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
def cleanup(event):
"""Clean up resources."""
plum.cleanup()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)
cloud_web_sesison = async_get_clientsession(hass, verify_ssl=True)
await plum.loadCloudData(cloud_web_sesison)
async def new_load(device):
"""Load light and sensor platforms when LogicalLoad is detected."""
await asyncio.wait(
[
hass.async_create_task(
discovery.async_load_platform(
hass, "light", DOMAIN, discovered=device, hass_config=conf
)
)
]
)
async def new_lightpad(device):
"""Load light and binary sensor platforms when Lightpad detected."""
await asyncio.wait(
[
hass.async_create_task(
discovery.async_load_platform(
hass, "light", DOMAIN, discovered=device, hass_config=conf
)
)
]
)
device_web_session = async_get_clientsession(hass, verify_ssl=False)
hass.async_create_task(
plum.discover(
hass.loop,
loadListener=new_load,
lightpadListener=new_lightpad,
websession=device_web_session,
)
)
return True

View File

@ -0,0 +1,62 @@
"""Config flow for Plum Lightpad."""
import logging
from typing import Any, Dict, Optional
from aiohttp import ContentTypeError
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import ConfigType
from .const import DOMAIN # pylint: disable=unused-import
from .utils import load_plum
_LOGGER = logging.getLogger(__name__)
class PlumLightpadConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Plum Lightpad integration."""
VERSION = 1
def _show_form(self, errors=None):
schema = {
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
return self.async_show_form(
step_id="user", data_schema=vol.Schema(schema), errors=errors or {},
)
async def async_step_user(
self, user_input: Optional[ConfigType] = None
) -> Dict[str, Any]:
"""Handle a flow initialized by the user or redirected to by import."""
if not user_input:
return self._show_form()
username = user_input[CONF_USERNAME]
password = user_input[CONF_PASSWORD]
# load Plum just so we know username/password work
try:
await load_plum(username, password, self.hass)
except (ContentTypeError, ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect/authenticate to Plum cloud: %s", str(ex))
return self._show_form({"base": "cannot_connect"})
await self.async_set_unique_id(username)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=username, data={CONF_USERNAME: username, CONF_PASSWORD: password}
)
async def async_step_import(
self, import_config: Optional[ConfigType]
) -> Dict[str, Any]:
"""Import a config entry from configuration.yaml."""
return await self.async_step_user(import_config)

View File

@ -1,4 +1,9 @@
"""Support for Plum Lightpad lights."""
import logging
from typing import Callable, List
from plumlightpad import Plum
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_HS_COLOR,
@ -6,30 +11,55 @@ from homeassistant.components.light import (
SUPPORT_COLOR,
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
import homeassistant.util.color as color_util
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Initialize the Plum Lightpad Light and GlowRing."""
if discovery_info is None:
return
plum = hass.data[DOMAIN]
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity]], None],
) -> None:
"""Set up Plum Lightpad dimmer lights and glow rings."""
entities = []
plum: Plum = hass.data[DOMAIN][entry.entry_id]
if "lpid" in discovery_info:
lightpad = plum.get_lightpad(discovery_info["lpid"])
entities.append(GlowRing(lightpad=lightpad))
def setup_entities(device) -> None:
entities = []
if "llid" in discovery_info:
logical_load = plum.get_load(discovery_info["llid"])
entities.append(PlumLight(load=logical_load))
if "lpid" in device:
lightpad = plum.get_lightpad(device["lpid"])
entities.append(GlowRing(lightpad=lightpad))
if entities:
async_add_entities(entities)
if "llid" in device:
logical_load = plum.get_load(device["llid"])
entities.append(PlumLight(load=logical_load))
if entities:
async_add_entities(entities)
async def new_load(device):
setup_entities(device)
async def new_lightpad(device):
setup_entities(device)
device_web_session = async_get_clientsession(hass, verify_ssl=False)
hass.loop.create_task(
plum.discover(
hass.loop,
loadListener=new_load,
lightpadListener=new_lightpad,
websession=device_web_session,
)
)
class PlumLight(LightEntity):
@ -64,6 +94,16 @@ class PlumLight(LightEntity):
"""Return the name of the switch if any."""
return self._load.name
@property
def device_info(self):
"""Return the device info."""
return {
"name": self.name,
"identifiers": {(DOMAIN, self.unique_id)},
"model": "Dimmer",
"manufacturer": "Plum",
}
@property
def brightness(self) -> int:
"""Return the brightness of this switch between 0..255."""
@ -145,6 +185,16 @@ class GlowRing(LightEntity):
"""Return the name of the switch if any."""
return self._name
@property
def device_info(self):
"""Return the device info."""
return {
"name": self.name,
"identifiers": {(DOMAIN, self.unique_id)},
"model": "Glow Ring",
"manufacturer": "Plum",
}
@property
def brightness(self) -> int:
"""Return the brightness of this switch between 0..255."""

View File

@ -2,6 +2,12 @@
"domain": "plum_lightpad",
"name": "Plum Lightpad",
"documentation": "https://www.home-assistant.io/integrations/plum_lightpad",
"requirements": ["plumlightpad==0.0.11"],
"codeowners": ["@ColinHarrington"]
"requirements": [
"plumlightpad==0.0.11"
],
"codeowners": [
"@ColinHarrington",
"@prystupa"
],
"config_flow": true
}

View File

@ -0,0 +1,18 @@
{
"config": {
"step": {
"user": {
"data": {
"username": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"single_instance_per_username_allowed": "Only one config entry per unique username is supported"
},
"error": {
"cannot_connect": "Unable to connect to Plum Cloud."
},
"step": {
"user": {
"data": {
"password": "Password",
"username": "Email"
},
"title": "Fill in your Plum Cloud login information"
}
}
},
"title": "Plum Lightpad"
}

View File

@ -0,0 +1,14 @@
"""Reusable utilities for the Plum Lightpad component."""
from plumlightpad import Plum
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
async def load_plum(username: str, password: str, hass: HomeAssistant) -> Plum:
"""Initialize Plum Lightpad API and load metadata stored in the cloud."""
plum = Plum(username, password)
cloud_web_session = async_get_clientsession(hass, verify_ssl=True)
await plum.loadCloudData(cloud_web_session)
return plum

View File

@ -122,6 +122,7 @@ FLOWS = [
"plaato",
"plex",
"plugwise",
"plum_lightpad",
"point",
"powerwall",
"ps4",