2019-02-13 20:21:14 +00:00
|
|
|
"""Support for deCONZ devices."""
|
2018-01-01 16:08:13 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-08-09 11:24:14 +00:00
|
|
|
from homeassistant import config_entries
|
2018-01-01 16:08:13 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_API_KEY,
|
|
|
|
CONF_HOST,
|
|
|
|
CONF_PORT,
|
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
|
|
|
)
|
2018-10-31 21:38:04 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv
|
2018-01-01 16:08:13 +00:00
|
|
|
|
2018-04-18 14:27:44 +00:00
|
|
|
# Loading the config flow file will register the flow
|
2019-04-05 00:48:24 +00:00
|
|
|
from .config_flow import get_master_gateway
|
2019-09-05 23:38:00 +00:00
|
|
|
from .const import CONF_BRIDGEID, CONF_MASTER_GATEWAY, DEFAULT_PORT, DOMAIN, _LOGGER
|
2018-10-31 21:38:04 +00:00
|
|
|
from .gateway import DeconzGateway
|
2018-01-01 16:08:13 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
DOMAIN: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Optional(CONF_API_KEY): cv.string,
|
|
|
|
vol.Optional(CONF_HOST): cv.string,
|
|
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
2018-01-01 16:08:13 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_DECONZ = "configure"
|
2018-04-29 14:16:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_FIELD = "field"
|
|
|
|
SERVICE_ENTITY = "entity"
|
|
|
|
SERVICE_DATA = "data"
|
2018-01-01 16:08:13 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_SCHEMA = vol.All(
|
|
|
|
vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Optional(SERVICE_ENTITY): cv.entity_id,
|
|
|
|
vol.Optional(SERVICE_FIELD): cv.matches_regex("/.*"),
|
|
|
|
vol.Required(SERVICE_DATA): dict,
|
|
|
|
vol.Optional(CONF_BRIDGEID): str,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD),
|
|
|
|
)
|
2018-01-01 16:08:13 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_DEVICE_REFRESH = "device_refresh"
|
2018-09-04 07:24:42 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SERVICE_DEVICE_REFRESCH_SCHEMA = vol.All(vol.Schema({vol.Optional(CONF_BRIDGEID): str}))
|
2019-04-05 00:48:24 +00:00
|
|
|
|
2018-02-14 00:23:04 +00:00
|
|
|
|
2018-03-13 07:47:45 +00:00
|
|
|
async def async_setup(hass, config):
|
2018-04-18 14:27:44 +00:00
|
|
|
"""Load configuration for deCONZ component.
|
2018-01-01 16:08:13 +00:00
|
|
|
|
2018-04-18 14:27:44 +00:00
|
|
|
Discovery has loaded the component if DOMAIN is not present in config.
|
|
|
|
"""
|
2019-04-05 00:48:24 +00:00
|
|
|
if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
|
|
|
|
deconz_config = config[DOMAIN]
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
|
|
|
DOMAIN,
|
|
|
|
context={"source": config_entries.SOURCE_IMPORT},
|
|
|
|
data=deconz_config,
|
|
|
|
)
|
|
|
|
)
|
2018-04-18 14:27:44 +00:00
|
|
|
return True
|
2018-01-01 16:08:13 +00:00
|
|
|
|
|
|
|
|
2018-04-23 16:00:16 +00:00
|
|
|
async def async_setup_entry(hass, config_entry):
|
|
|
|
"""Set up a deCONZ bridge for a config entry.
|
2018-01-01 16:08:13 +00:00
|
|
|
|
|
|
|
Load config, group, light and sensor data for server information.
|
|
|
|
Start websocket for push notification of state changes from deCONZ.
|
|
|
|
"""
|
2019-04-05 00:48:24 +00:00
|
|
|
if DOMAIN not in hass.data:
|
|
|
|
hass.data[DOMAIN] = {}
|
|
|
|
|
|
|
|
if not config_entry.options:
|
2019-09-05 23:38:00 +00:00
|
|
|
await async_update_master_gateway(hass, config_entry)
|
2018-04-23 16:00:16 +00:00
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
gateway = DeconzGateway(hass, config_entry)
|
2018-05-05 14:11:00 +00:00
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
if not await gateway.async_setup():
|
2018-01-01 16:08:13 +00:00
|
|
|
return False
|
|
|
|
|
2019-04-05 00:48:24 +00:00
|
|
|
hass.data[DOMAIN][gateway.bridgeid] = gateway
|
2019-02-27 20:04:55 +00:00
|
|
|
|
2019-04-05 00:48:24 +00:00
|
|
|
await gateway.async_update_device_registry()
|
2018-08-24 17:37:22 +00:00
|
|
|
|
2018-03-13 07:47:45 +00:00
|
|
|
async def async_configure(call):
|
2018-01-01 16:08:13 +00:00
|
|
|
"""Set attribute of device in deCONZ.
|
|
|
|
|
2018-10-26 07:15:26 +00:00
|
|
|
Entity is used to resolve to a device path (e.g. '/lights/1').
|
|
|
|
Field is a string representing either a full path
|
|
|
|
(e.g. '/lights/1/state') when entity is not specified, or a
|
|
|
|
subpath (e.g. '/state') when used together with entity.
|
2018-01-01 16:08:13 +00:00
|
|
|
Data is a json object with what data you want to alter
|
|
|
|
e.g. data={'on': true}.
|
|
|
|
{
|
|
|
|
"field": "/lights/1/state",
|
|
|
|
"data": {"on": true}
|
|
|
|
}
|
|
|
|
See Dresden Elektroniks REST API documentation for details:
|
|
|
|
http://dresden-elektronik.github.io/deconz-rest-doc/rest/
|
|
|
|
"""
|
2019-07-31 19:25:30 +00:00
|
|
|
field = call.data.get(SERVICE_FIELD, "")
|
2018-02-14 00:23:04 +00:00
|
|
|
entity_id = call.data.get(SERVICE_ENTITY)
|
2019-04-05 00:48:24 +00:00
|
|
|
data = call.data[SERVICE_DATA]
|
|
|
|
|
|
|
|
gateway = get_master_gateway(hass)
|
|
|
|
if CONF_BRIDGEID in call.data:
|
|
|
|
gateway = hass.data[DOMAIN][call.data[CONF_BRIDGEID]]
|
2018-10-31 21:38:04 +00:00
|
|
|
|
2018-02-14 00:23:04 +00:00
|
|
|
if entity_id:
|
2018-10-26 07:15:26 +00:00
|
|
|
try:
|
2018-10-31 21:38:04 +00:00
|
|
|
field = gateway.deconz_ids[entity_id] + field
|
2018-10-26 07:15:26 +00:00
|
|
|
except KeyError:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("Could not find the entity %s", entity_id)
|
2018-02-14 00:23:04 +00:00
|
|
|
return
|
2018-09-04 07:24:42 +00:00
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
await gateway.api.async_put_state(field, data)
|
2018-09-04 07:24:42 +00:00
|
|
|
|
2018-01-01 16:08:13 +00:00
|
|
|
hass.services.async_register(
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA
|
|
|
|
)
|
2018-01-01 16:08:13 +00:00
|
|
|
|
2018-09-04 07:24:42 +00:00
|
|
|
async def async_refresh_devices(call):
|
|
|
|
"""Refresh available devices from deCONZ."""
|
2019-04-05 00:48:24 +00:00
|
|
|
gateway = get_master_gateway(hass)
|
|
|
|
if CONF_BRIDGEID in call.data:
|
|
|
|
gateway = hass.data[DOMAIN][call.data[CONF_BRIDGEID]]
|
2018-09-04 07:24:42 +00:00
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
groups = set(gateway.api.groups.keys())
|
|
|
|
lights = set(gateway.api.lights.keys())
|
|
|
|
scenes = set(gateway.api.scenes.keys())
|
|
|
|
sensors = set(gateway.api.sensors.keys())
|
2018-09-04 07:24:42 +00:00
|
|
|
|
2019-03-24 18:27:32 +00:00
|
|
|
await gateway.api.async_load_parameters()
|
2018-09-04 07:24:42 +00:00
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
gateway.async_add_device_callback(
|
2019-07-31 19:25:30 +00:00
|
|
|
"group",
|
|
|
|
[
|
|
|
|
group
|
|
|
|
for group_id, group in gateway.api.groups.items()
|
|
|
|
if group_id not in groups
|
|
|
|
],
|
2018-09-04 07:24:42 +00:00
|
|
|
)
|
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
gateway.async_add_device_callback(
|
2019-07-31 19:25:30 +00:00
|
|
|
"light",
|
|
|
|
[
|
|
|
|
light
|
|
|
|
for light_id, light in gateway.api.lights.items()
|
|
|
|
if light_id not in lights
|
|
|
|
],
|
2018-09-04 07:24:42 +00:00
|
|
|
)
|
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
gateway.async_add_device_callback(
|
2019-07-31 19:25:30 +00:00
|
|
|
"scene",
|
|
|
|
[
|
|
|
|
scene
|
|
|
|
for scene_id, scene in gateway.api.scenes.items()
|
|
|
|
if scene_id not in scenes
|
|
|
|
],
|
2018-09-04 07:24:42 +00:00
|
|
|
)
|
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
gateway.async_add_device_callback(
|
2019-07-31 19:25:30 +00:00
|
|
|
"sensor",
|
|
|
|
[
|
|
|
|
sensor
|
|
|
|
for sensor_id, sensor in gateway.api.sensors.items()
|
|
|
|
if sensor_id not in sensors
|
|
|
|
],
|
2018-09-04 07:24:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
hass.services.async_register(
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN,
|
|
|
|
SERVICE_DEVICE_REFRESH,
|
|
|
|
async_refresh_devices,
|
|
|
|
schema=SERVICE_DEVICE_REFRESCH_SCHEMA,
|
|
|
|
)
|
2018-09-04 07:24:42 +00:00
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
|
2018-01-01 16:08:13 +00:00
|
|
|
return True
|
2018-04-29 14:16:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_unload_entry(hass, config_entry):
|
|
|
|
"""Unload deCONZ config entry."""
|
2019-04-05 00:48:24 +00:00
|
|
|
gateway = hass.data[DOMAIN].pop(config_entry.data[CONF_BRIDGEID])
|
|
|
|
|
|
|
|
if not hass.data[DOMAIN]:
|
|
|
|
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
|
|
|
|
hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
|
2019-05-27 04:56:00 +00:00
|
|
|
|
2019-04-05 00:48:24 +00:00
|
|
|
elif gateway.master:
|
2019-09-05 23:38:00 +00:00
|
|
|
await async_update_master_gateway(hass, config_entry)
|
2019-04-05 00:48:24 +00:00
|
|
|
new_master_gateway = next(iter(hass.data[DOMAIN].values()))
|
2019-09-05 23:38:00 +00:00
|
|
|
await async_update_master_gateway(hass, new_master_gateway.config_entry)
|
2019-04-05 00:48:24 +00:00
|
|
|
|
2018-10-31 21:38:04 +00:00
|
|
|
return await gateway.async_reset()
|
2019-04-05 00:48:24 +00:00
|
|
|
|
|
|
|
|
2019-09-05 23:38:00 +00:00
|
|
|
async def async_update_master_gateway(hass, config_entry):
|
|
|
|
"""Update master gateway boolean.
|
2019-04-05 00:48:24 +00:00
|
|
|
|
|
|
|
Called by setup_entry and unload_entry.
|
|
|
|
Makes sure there is always one master available.
|
|
|
|
"""
|
|
|
|
master = not get_master_gateway(hass)
|
|
|
|
|
2019-09-05 23:38:00 +00:00
|
|
|
old_options = dict(config_entry.options)
|
|
|
|
|
|
|
|
new_options = {CONF_MASTER_GATEWAY: master}
|
|
|
|
|
|
|
|
options = {**old_options, **new_options}
|
2019-04-05 00:48:24 +00:00
|
|
|
|
|
|
|
hass.config_entries.async_update_entry(config_entry, options=options)
|