mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
Add lutron_caseta config entries (#34133)
* lutron_caseta: allow for multiple bridges; use config entries Refactor to use config entries/flows, but only implemented import (async_setup) flow handler for now. * lutron_caseta: config_flow.py pylint hint Co-Authored-By: Martin Hjelmare <marhje52@gmail.com> * lutron_caseta: tweaks to __init__.py per PR feedback * lutron_caseta: add config_flow tests * lutron_caseta: verify connectivity to bridge check connectivity before creating config entry; cleanup translation/strings * lutron_caseta: allow for multiple bridges; use config entries Refactor to use config entries/flows, but only implemented import (async_setup) flow handler for now. * lutron_caseta: config_flow.py pylint hint Co-Authored-By: Martin Hjelmare <marhje52@gmail.com> * lutron_caseta: tweaks to __init__.py per PR feedback * lutron_caseta: add config_flow tests * lutron_caseta: verify connectivity to bridge check connectivity before creating config entry; cleanup translation/strings * lutron_caseta: add error logging when exception is encountered checking connectivity * lutron_caseta: tests mock bridge creation, not ha-side connectivity check * lutron_caseta: catch more specific Error types while checking bridge conn. Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
9d83059f14
commit
d072091926
@ -4,30 +4,30 @@ import logging
|
|||||||
from pylutron_caseta.smartbridge import Smartbridge
|
from pylutron_caseta.smartbridge import Smartbridge
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
from homeassistant.helpers import discovery
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from .const import CONF_CA_CERTS, CONF_CERTFILE, CONF_KEYFILE
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
LUTRON_CASETA_SMARTBRIDGE = "lutron_smartbridge"
|
|
||||||
|
|
||||||
DOMAIN = "lutron_caseta"
|
DOMAIN = "lutron_caseta"
|
||||||
|
DATA_BRIDGE_CONFIG = "lutron_caseta_bridges"
|
||||||
CONF_KEYFILE = "keyfile"
|
|
||||||
CONF_CERTFILE = "certfile"
|
|
||||||
CONF_CA_CERTS = "ca_certs"
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[
|
||||||
{
|
{
|
||||||
vol.Required(CONF_HOST): cv.string,
|
vol.Required(CONF_HOST): cv.string,
|
||||||
vol.Required(CONF_KEYFILE): cv.string,
|
vol.Required(CONF_KEYFILE): cv.string,
|
||||||
vol.Required(CONF_CERTFILE): cv.string,
|
vol.Required(CONF_CERTFILE): cv.string,
|
||||||
vol.Required(CONF_CA_CERTS): cv.string,
|
vol.Required(CONF_CA_CERTS): cv.string,
|
||||||
}
|
}
|
||||||
|
],
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
@ -39,29 +39,57 @@ LUTRON_CASETA_COMPONENTS = ["light", "switch", "cover", "scene", "fan", "binary_
|
|||||||
async def async_setup(hass, base_config):
|
async def async_setup(hass, base_config):
|
||||||
"""Set up the Lutron component."""
|
"""Set up the Lutron component."""
|
||||||
|
|
||||||
config = base_config.get(DOMAIN)
|
bridge_configs = base_config.get(DOMAIN)
|
||||||
keyfile = hass.config.path(config[CONF_KEYFILE])
|
|
||||||
certfile = hass.config.path(config[CONF_CERTFILE])
|
if not bridge_configs:
|
||||||
ca_certs = hass.config.path(config[CONF_CA_CERTS])
|
return True
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
|
for config in bridge_configs:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
# extract the config keys one-by-one just to be explicit
|
||||||
|
data={
|
||||||
|
CONF_HOST: config[CONF_HOST],
|
||||||
|
CONF_KEYFILE: config[CONF_KEYFILE],
|
||||||
|
CONF_CERTFILE: config[CONF_CERTFILE],
|
||||||
|
CONF_CA_CERTS: config[CONF_CA_CERTS],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
"""Set up a bridge from a config entry."""
|
||||||
|
|
||||||
|
host = config_entry.data[CONF_HOST]
|
||||||
|
keyfile = config_entry.data[CONF_KEYFILE]
|
||||||
|
certfile = config_entry.data[CONF_CERTFILE]
|
||||||
|
ca_certs = config_entry.data[CONF_CA_CERTS]
|
||||||
|
|
||||||
bridge = Smartbridge.create_tls(
|
bridge = Smartbridge.create_tls(
|
||||||
hostname=config[CONF_HOST],
|
hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs
|
||||||
keyfile=keyfile,
|
|
||||||
certfile=certfile,
|
|
||||||
ca_certs=ca_certs,
|
|
||||||
)
|
)
|
||||||
hass.data[LUTRON_CASETA_SMARTBRIDGE] = bridge
|
|
||||||
await bridge.connect()
|
await bridge.connect()
|
||||||
if not hass.data[LUTRON_CASETA_SMARTBRIDGE].is_connected():
|
if not bridge.is_connected():
|
||||||
_LOGGER.error(
|
_LOGGER.error("Unable to connect to Lutron Caseta bridge at %s", host)
|
||||||
"Unable to connect to Lutron smartbridge at %s", config[CONF_HOST]
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_LOGGER.info("Connected to Lutron smartbridge at %s", config[CONF_HOST])
|
_LOGGER.debug("Connected to Lutron Caseta bridge at %s", host)
|
||||||
|
|
||||||
|
# Store this bridge (keyed by entry_id) so it can be retrieved by the
|
||||||
|
# components we're setting up.
|
||||||
|
hass.data[DOMAIN][config_entry.entry_id] = bridge
|
||||||
|
|
||||||
for component in LUTRON_CASETA_COMPONENTS:
|
for component in LUTRON_CASETA_COMPONENTS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
discovery.async_load_platform(hass, component, DOMAIN, {}, config)
|
hass.config_entries.async_forward_entry_setup(config_entry, component)
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -6,14 +6,20 @@ from homeassistant.components.binary_sensor import (
|
|||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
|
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Lutron Caseta lights."""
|
"""Set up the Lutron Caseta binary_sensor platform.
|
||||||
|
|
||||||
|
Adds occupancy groups from the Caseta bridge associated with the
|
||||||
|
config_entry as binary_sensor entities.
|
||||||
|
"""
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
|
bridge = hass.data[CASETA_DOMAIN][config_entry.entry_id]
|
||||||
occupancy_groups = bridge.occupancy_groups
|
occupancy_groups = bridge.occupancy_groups
|
||||||
|
|
||||||
for occupancy_group in occupancy_groups.values():
|
for occupancy_group in occupancy_groups.values():
|
||||||
entity = LutronOccupancySensor(occupancy_group, bridge)
|
entity = LutronOccupancySensor(occupancy_group, bridge)
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
107
homeassistant/components/lutron_caseta/config_flow.py
Normal file
107
homeassistant/components/lutron_caseta/config_flow.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
"""Config flow for Lutron Caseta."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pylutron_caseta.smartbridge import Smartbridge
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_HOST
|
||||||
|
|
||||||
|
from . import DOMAIN # pylint: disable=unused-import
|
||||||
|
from .const import (
|
||||||
|
ABORT_REASON_ALREADY_CONFIGURED,
|
||||||
|
ABORT_REASON_CANNOT_CONNECT,
|
||||||
|
CONF_CA_CERTS,
|
||||||
|
CONF_CERTFILE,
|
||||||
|
CONF_KEYFILE,
|
||||||
|
ERROR_CANNOT_CONNECT,
|
||||||
|
STEP_IMPORT_FAILED,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ENTRY_DEFAULT_TITLE = "Caséta bridge"
|
||||||
|
|
||||||
|
|
||||||
|
class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle Lutron Caseta config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize a Lutron Caseta flow."""
|
||||||
|
self.data = {}
|
||||||
|
|
||||||
|
async def async_step_import(self, import_info):
|
||||||
|
"""Import a new Caseta bridge as a config entry.
|
||||||
|
|
||||||
|
This flow is triggered by `async_setup`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Abort if existing entry with matching host exists.
|
||||||
|
host = import_info[CONF_HOST]
|
||||||
|
if any(
|
||||||
|
host == entry.data[CONF_HOST] for entry in self._async_current_entries()
|
||||||
|
):
|
||||||
|
return self.async_abort(reason=ABORT_REASON_ALREADY_CONFIGURED)
|
||||||
|
|
||||||
|
# Store the imported config for other steps in this flow to access.
|
||||||
|
self.data[CONF_HOST] = host
|
||||||
|
self.data[CONF_KEYFILE] = import_info[CONF_KEYFILE]
|
||||||
|
self.data[CONF_CERTFILE] = import_info[CONF_CERTFILE]
|
||||||
|
self.data[CONF_CA_CERTS] = import_info[CONF_CA_CERTS]
|
||||||
|
|
||||||
|
if not await self.async_validate_connectable_bridge_config():
|
||||||
|
# Ultimately we won't have a dedicated step for import failure, but
|
||||||
|
# in order to keep configuration.yaml-based configs transparently
|
||||||
|
# working without requiring further actions from the user, we don't
|
||||||
|
# display a form at all before creating a config entry in the
|
||||||
|
# default case, so we're only going to show a form in case the
|
||||||
|
# import fails.
|
||||||
|
# This will change in an upcoming release where UI-based config flow
|
||||||
|
# will become the default for the Lutron Caseta integration (which
|
||||||
|
# will require users to go through a confirmation flow for imports).
|
||||||
|
return await self.async_step_import_failed()
|
||||||
|
|
||||||
|
return self.async_create_entry(title=ENTRY_DEFAULT_TITLE, data=self.data)
|
||||||
|
|
||||||
|
async def async_step_import_failed(self, user_input=None):
|
||||||
|
"""Make failed import surfaced to user."""
|
||||||
|
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id=STEP_IMPORT_FAILED,
|
||||||
|
description_placeholders={"host": self.data[CONF_HOST]},
|
||||||
|
errors={"base": ERROR_CANNOT_CONNECT},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_abort(reason=ABORT_REASON_CANNOT_CONNECT)
|
||||||
|
|
||||||
|
async def async_validate_connectable_bridge_config(self):
|
||||||
|
"""Check if we can connect to the bridge with the current config."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
bridge = Smartbridge.create_tls(
|
||||||
|
hostname=self.data[CONF_HOST],
|
||||||
|
keyfile=self.data[CONF_KEYFILE],
|
||||||
|
certfile=self.data[CONF_CERTFILE],
|
||||||
|
ca_certs=self.data[CONF_CA_CERTS],
|
||||||
|
)
|
||||||
|
|
||||||
|
await bridge.connect()
|
||||||
|
if not bridge.is_connected():
|
||||||
|
return False
|
||||||
|
|
||||||
|
await bridge.close()
|
||||||
|
return True
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
_LOGGER.error(
|
||||||
|
"Error while checking connectivity to bridge %s", self.data[CONF_HOST],
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Unknown exception while checking connectivity to bridge %s",
|
||||||
|
self.data[CONF_HOST],
|
||||||
|
)
|
||||||
|
return False
|
10
homeassistant/components/lutron_caseta/const.py
Normal file
10
homeassistant/components/lutron_caseta/const.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""Lutron Caseta constants."""
|
||||||
|
|
||||||
|
CONF_KEYFILE = "keyfile"
|
||||||
|
CONF_CERTFILE = "certfile"
|
||||||
|
CONF_CA_CERTS = "ca_certs"
|
||||||
|
|
||||||
|
STEP_IMPORT_FAILED = "import_failed"
|
||||||
|
ERROR_CANNOT_CONNECT = "cannot_connect"
|
||||||
|
ABORT_REASON_CANNOT_CONNECT = "cannot_connect"
|
||||||
|
ABORT_REASON_ALREADY_CONFIGURED = "already_configured"
|
@ -10,16 +10,22 @@ from homeassistant.components.cover import (
|
|||||||
CoverEntity,
|
CoverEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
|
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Lutron Caseta shades as a cover device."""
|
"""Set up the Lutron Caseta cover platform.
|
||||||
|
|
||||||
|
Adds shades from the Caseta bridge associated with the config_entry as
|
||||||
|
cover entities.
|
||||||
|
"""
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
|
bridge = hass.data[CASETA_DOMAIN][config_entry.entry_id]
|
||||||
cover_devices = bridge.get_devices_by_domain(DOMAIN)
|
cover_devices = bridge.get_devices_by_domain(DOMAIN)
|
||||||
|
|
||||||
for cover_device in cover_devices:
|
for cover_device in cover_devices:
|
||||||
entity = LutronCasetaCover(cover_device, bridge)
|
entity = LutronCasetaCover(cover_device, bridge)
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
@ -13,7 +13,7 @@ from homeassistant.components.fan import (
|
|||||||
FanEntity,
|
FanEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
|
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -36,10 +36,15 @@ SPEED_TO_VALUE = {
|
|||||||
FAN_SPEEDS = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
FAN_SPEEDS = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Lutron fan."""
|
"""Set up the Lutron Caseta fan platform.
|
||||||
|
|
||||||
|
Adds fan controllers from the Caseta bridge associated with the config_entry
|
||||||
|
as fan entities.
|
||||||
|
"""
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
|
bridge = hass.data[CASETA_DOMAIN][config_entry.entry_id]
|
||||||
fan_devices = bridge.get_devices_by_domain(DOMAIN)
|
fan_devices = bridge.get_devices_by_domain(DOMAIN)
|
||||||
|
|
||||||
for fan_device in fan_devices:
|
for fan_device in fan_devices:
|
||||||
|
@ -8,7 +8,7 @@ from homeassistant.components.light import (
|
|||||||
LightEntity,
|
LightEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
|
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -23,11 +23,17 @@ def to_hass_level(level):
|
|||||||
return int((level * 255) // 100)
|
return int((level * 255) // 100)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Lutron Caseta lights."""
|
"""Set up the Lutron Caseta light platform.
|
||||||
|
|
||||||
|
Adds dimmers from the Caseta bridge associated with the config_entry as
|
||||||
|
light entities.
|
||||||
|
"""
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
|
bridge = hass.data[CASETA_DOMAIN][config_entry.entry_id]
|
||||||
light_devices = bridge.get_devices_by_domain(DOMAIN)
|
light_devices = bridge.get_devices_by_domain(DOMAIN)
|
||||||
|
|
||||||
for light_device in light_devices:
|
for light_device in light_devices:
|
||||||
entity = LutronCasetaLight(light_device, bridge)
|
entity = LutronCasetaLight(light_device, bridge)
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
"name": "Lutron Caséta",
|
"name": "Lutron Caséta",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/lutron_caseta",
|
"documentation": "https://www.home-assistant.io/integrations/lutron_caseta",
|
||||||
"requirements": ["pylutron-caseta==0.6.1"],
|
"requirements": ["pylutron-caseta==0.6.1"],
|
||||||
"codeowners": ["@swails"]
|
"codeowners": ["@swails"],
|
||||||
|
"config_flow": true
|
||||||
}
|
}
|
@ -4,16 +4,22 @@ from typing import Any
|
|||||||
|
|
||||||
from homeassistant.components.scene import Scene
|
from homeassistant.components.scene import Scene
|
||||||
|
|
||||||
from . import LUTRON_CASETA_SMARTBRIDGE
|
from . import DOMAIN as CASETA_DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Lutron Caseta lights."""
|
"""Set up the Lutron Caseta scene platform.
|
||||||
|
|
||||||
|
Adds scenes from the Caseta bridge associated with the config_entry as
|
||||||
|
scene entities.
|
||||||
|
"""
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
|
bridge = hass.data[CASETA_DOMAIN][config_entry.entry_id]
|
||||||
scenes = bridge.get_scenes()
|
scenes = bridge.get_scenes()
|
||||||
|
|
||||||
for scene in scenes:
|
for scene in scenes:
|
||||||
entity = LutronCasetaScene(scenes[scene], bridge)
|
entity = LutronCasetaScene(scenes[scene], bridge)
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
|
17
homeassistant/components/lutron_caseta/strings.json
Normal file
17
homeassistant/components/lutron_caseta/strings.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"import_failed": {
|
||||||
|
"title": "Failed to import Caséta bridge configuration.",
|
||||||
|
"description": "Couldn’t setup bridge (host: {host}) imported from configuration.yaml."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect to Caséta bridge; check your host and certificate configuration."
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Caséta bridge already configured.",
|
||||||
|
"cannot_connect": "Cancelled setup of Caséta bridge due to connection failure."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,15 +3,20 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.switch import DOMAIN, SwitchEntity
|
from homeassistant.components.switch import DOMAIN, SwitchEntity
|
||||||
|
|
||||||
from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice
|
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Lutron switch."""
|
"""Set up the Lutron Caseta switch platform.
|
||||||
|
|
||||||
|
Adds switches from the Caseta bridge associated with the config_entry as
|
||||||
|
switch entities.
|
||||||
|
"""
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
|
bridge = hass.data[CASETA_DOMAIN][config_entry.entry_id]
|
||||||
switch_devices = bridge.get_devices_by_domain(DOMAIN)
|
switch_devices = bridge.get_devices_by_domain(DOMAIN)
|
||||||
|
|
||||||
for switch_device in switch_devices:
|
for switch_device in switch_devices:
|
||||||
|
@ -1,3 +1,17 @@
|
|||||||
{
|
{
|
||||||
"title": "Lutron Cas\u00e9ta"
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"import_failed": {
|
||||||
|
"title": "Failed to import Caséta bridge configuration.",
|
||||||
|
"description": "Couldn’t import Caséta bridge (host: {host}) from configuration.yaml."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect to Caséta bridge; check your host and certificate configuration."
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Caséta bridge already configured.",
|
||||||
|
"cannot_connect": "Cancelled setup of Caséta bridge due to connection failure."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -81,6 +81,7 @@ FLOWS = [
|
|||||||
"locative",
|
"locative",
|
||||||
"logi_circle",
|
"logi_circle",
|
||||||
"luftdaten",
|
"luftdaten",
|
||||||
|
"lutron_caseta",
|
||||||
"mailgun",
|
"mailgun",
|
||||||
"melcloud",
|
"melcloud",
|
||||||
"met",
|
"met",
|
||||||
|
@ -599,6 +599,9 @@ pylinky==0.4.0
|
|||||||
# homeassistant.components.litejet
|
# homeassistant.components.litejet
|
||||||
pylitejet==0.1
|
pylitejet==0.1
|
||||||
|
|
||||||
|
# homeassistant.components.lutron_caseta
|
||||||
|
pylutron-caseta==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.mailgun
|
# homeassistant.components.mailgun
|
||||||
pymailgunner==1.4
|
pymailgunner==1.4
|
||||||
|
|
||||||
|
1
tests/components/lutron_caseta/__init__.py
Normal file
1
tests/components/lutron_caseta/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Lutron Caseta integration."""
|
122
tests/components/lutron_caseta/test_config_flow.py
Normal file
122
tests/components/lutron_caseta/test_config_flow.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
"""Test the Lutron Caseta config flow."""
|
||||||
|
from asynctest import patch
|
||||||
|
from pylutron_caseta.smartbridge import Smartbridge
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.components.lutron_caseta import DOMAIN
|
||||||
|
import homeassistant.components.lutron_caseta.config_flow as CasetaConfigFlow
|
||||||
|
from homeassistant.components.lutron_caseta.const import (
|
||||||
|
CONF_CA_CERTS,
|
||||||
|
CONF_CERTFILE,
|
||||||
|
CONF_KEYFILE,
|
||||||
|
ERROR_CANNOT_CONNECT,
|
||||||
|
STEP_IMPORT_FAILED,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_HOST
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
class MockBridge:
|
||||||
|
"""Mock Lutron bridge that emulates configured connected status."""
|
||||||
|
|
||||||
|
def __init__(self, can_connect=True):
|
||||||
|
"""Initialize MockBridge instance with configured mock connectivity."""
|
||||||
|
self.can_connect = can_connect
|
||||||
|
self.is_currently_connected = False
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
"""Connect the mock bridge."""
|
||||||
|
if self.can_connect:
|
||||||
|
self.is_currently_connected = True
|
||||||
|
|
||||||
|
def is_connected(self):
|
||||||
|
"""Return whether the mock bridge is connected."""
|
||||||
|
return self.is_currently_connected
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
"""Close the mock bridge connection."""
|
||||||
|
self.is_currently_connected = False
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_import_flow(hass):
|
||||||
|
"""Test a bridge entry gets created and set up during the import flow."""
|
||||||
|
|
||||||
|
entry_mock_data = {
|
||||||
|
CONF_HOST: "1.1.1.1",
|
||||||
|
CONF_KEYFILE: "",
|
||||||
|
CONF_CERTFILE: "",
|
||||||
|
CONF_CA_CERTS: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.lutron_caseta.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry, patch.object(Smartbridge, "create_tls") as create_tls:
|
||||||
|
create_tls.return_value = MockBridge(can_connect=True)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data=entry_mock_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == CasetaConfigFlow.ENTRY_DEFAULT_TITLE
|
||||||
|
assert result["data"] == entry_mock_data
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_cannot_connect(hass):
|
||||||
|
"""Test checking for connection and cannot_connect error."""
|
||||||
|
|
||||||
|
entry_mock_data = {
|
||||||
|
CONF_HOST: "not.a.valid.host",
|
||||||
|
CONF_KEYFILE: "",
|
||||||
|
CONF_CERTFILE: "",
|
||||||
|
CONF_CA_CERTS: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.lutron_caseta.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry, patch.object(Smartbridge, "create_tls") as create_tls:
|
||||||
|
create_tls.return_value = MockBridge(can_connect=False)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data=entry_mock_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == STEP_IMPORT_FAILED
|
||||||
|
assert result["errors"] == {"base": ERROR_CANNOT_CONNECT}
|
||||||
|
# validate setup_entry was not called
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_duplicate_bridge_import(hass):
|
||||||
|
"""Test that creating a bridge entry with a duplicate host errors."""
|
||||||
|
|
||||||
|
entry_mock_data = {
|
||||||
|
CONF_HOST: "1.1.1.1",
|
||||||
|
CONF_KEYFILE: "",
|
||||||
|
CONF_CERTFILE: "",
|
||||||
|
CONF_CA_CERTS: "",
|
||||||
|
}
|
||||||
|
mock_entry = MockConfigEntry(domain=DOMAIN, data=entry_mock_data)
|
||||||
|
mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.lutron_caseta.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
# Mock entry added, try initializing flow with duplicate host
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data=entry_mock_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == CasetaConfigFlow.ABORT_REASON_ALREADY_CONFIGURED
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 0
|
Loading…
x
Reference in New Issue
Block a user