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
parent
6c7355785a
commit
e3b90ea3f7
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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%]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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
|
|
@ -122,6 +122,7 @@ FLOWS = [
|
|||
"plaato",
|
||||
"plex",
|
||||
"plugwise",
|
||||
"plum_lightpad",
|
||||
"point",
|
||||
"powerwall",
|
||||
"ps4",
|
||||
|
|
Loading…
Reference in New Issue