2019-02-14 15:01:46 +00:00
""" Support for the Philips Hue system. """
2020-10-11 19:01:49 +00:00
import asyncio
2017-12-10 18:15:01 +00:00
import logging
2019-12-16 18:45:09 +00:00
from aiohue . util import normalize_bridge_id
2021-04-13 16:31:23 +00:00
import voluptuous as vol
2017-12-10 18:15:01 +00:00
2019-12-16 11:27:43 +00:00
from homeassistant import config_entries , core
2020-02-05 21:57:17 +00:00
from homeassistant . components import persistent_notification
2021-04-13 16:31:23 +00:00
from homeassistant . helpers import config_validation as cv , device_registry as dr
from homeassistant . helpers . service import verify_domain_control
2018-03-30 03:15:40 +00:00
2021-04-13 16:31:23 +00:00
from . bridge import HueBridge
from . const import (
2020-10-11 19:01:49 +00:00
ATTR_GROUP_NAME ,
ATTR_SCENE_NAME ,
2021-04-13 16:31:23 +00:00
ATTR_TRANSITION ,
2020-02-11 16:50:07 +00:00
CONF_ALLOW_HUE_GROUPS ,
CONF_ALLOW_UNREACHABLE ,
DEFAULT_ALLOW_HUE_GROUPS ,
DEFAULT_ALLOW_UNREACHABLE ,
DOMAIN ,
)
2017-12-10 18:15:01 +00:00
_LOGGER = logging . getLogger ( __name__ )
2021-04-13 16:31:23 +00:00
SERVICE_HUE_SCENE = " hue_activate_scene "
2018-03-04 05:28:04 +00:00
2019-12-16 11:27:43 +00:00
async def async_setup_entry (
hass : core . HomeAssistant , entry : config_entries . ConfigEntry
) :
2018-03-30 03:15:40 +00:00
""" Set up a bridge from a config entry. """
2018-03-04 05:28:04 +00:00
2020-07-02 12:12:24 +00:00
# Migrate allow_unreachable from config entry data to config entry options
if (
CONF_ALLOW_UNREACHABLE not in entry . options
and CONF_ALLOW_UNREACHABLE in entry . data
and entry . data [ CONF_ALLOW_UNREACHABLE ] != DEFAULT_ALLOW_UNREACHABLE
) :
options = {
* * entry . options ,
CONF_ALLOW_UNREACHABLE : entry . data [ CONF_ALLOW_UNREACHABLE ] ,
}
data = entry . data . copy ( )
data . pop ( CONF_ALLOW_UNREACHABLE )
hass . config_entries . async_update_entry ( entry , data = data , options = options )
# Migrate allow_hue_groups from config entry data to config entry options
if (
CONF_ALLOW_HUE_GROUPS not in entry . options
and CONF_ALLOW_HUE_GROUPS in entry . data
and entry . data [ CONF_ALLOW_HUE_GROUPS ] != DEFAULT_ALLOW_HUE_GROUPS
) :
options = {
* * entry . options ,
CONF_ALLOW_HUE_GROUPS : entry . data [ CONF_ALLOW_HUE_GROUPS ] ,
}
data = entry . data . copy ( )
data . pop ( CONF_ALLOW_HUE_GROUPS )
hass . config_entries . async_update_entry ( entry , data = data , options = options )
bridge = HueBridge ( hass , entry )
2018-08-29 15:04:04 +00:00
if not await bridge . async_setup ( ) :
return False
2021-04-13 16:31:23 +00:00
_register_services ( hass )
2018-08-29 15:04:04 +00:00
config = bridge . api . config
2019-12-16 11:27:43 +00:00
# For backwards compat
2020-07-10 16:37:36 +00:00
unique_id = normalize_bridge_id ( config . bridgeid )
2019-12-16 11:27:43 +00:00
if entry . unique_id is None :
2020-07-10 16:37:36 +00:00
hass . config_entries . async_update_entry ( entry , unique_id = unique_id )
# For recovering from bug where we incorrectly assumed homekit ID = bridge ID
elif entry . unique_id != unique_id :
# Find entries with this unique ID
other_entry = next (
(
entry
for entry in hass . config_entries . async_entries ( DOMAIN )
if entry . unique_id == unique_id
) ,
None ,
2019-12-16 11:27:43 +00:00
)
2020-07-10 16:37:36 +00:00
if other_entry is None :
# If no other entry, update unique ID of this entry ID.
hass . config_entries . async_update_entry ( entry , unique_id = unique_id )
elif other_entry . source == config_entries . SOURCE_IGNORE :
# There is another entry but it is ignored, delete that one and update this one
hass . async_create_task (
hass . config_entries . async_remove ( other_entry . entry_id )
)
hass . config_entries . async_update_entry ( entry , unique_id = unique_id )
else :
# There is another entry that already has the right unique ID. Delete this entry
hass . async_create_task ( hass . config_entries . async_remove ( entry . entry_id ) )
return False
2018-08-29 15:04:04 +00:00
device_registry = await dr . async_get_registry ( hass )
device_registry . async_get_or_create (
2018-09-17 11:39:30 +00:00
config_entry_id = entry . entry_id ,
2019-07-31 19:25:30 +00:00
connections = { ( dr . CONNECTION_NETWORK_MAC , config . mac ) } ,
identifiers = { ( DOMAIN , config . bridgeid ) } ,
manufacturer = " Signify " ,
2018-08-29 15:04:04 +00:00
name = config . name ,
2019-02-19 05:31:42 +00:00
model = config . modelid ,
sw_version = config . swversion ,
2018-08-29 15:04:04 +00:00
)
2020-02-05 21:57:17 +00:00
if config . modelid == " BSB002 " and config . swversion < " 1935144040 " :
persistent_notification . async_create (
hass ,
" Your Hue hub has a known security vulnerability ([CVE-2020-6007](https://cve.circl.lu/cve/CVE-2020-6007)). Go to the Hue app and check for software updates. " ,
" Signify Hue " ,
" hue_hub_firmware " ,
)
elif config . swupdate2_bridge_state == " readytoinstall " :
err = (
" Please check for software updates of the bridge in the Philips Hue App. " ,
" Signify Hue " ,
" hue_hub_firmware " ,
)
2019-02-19 05:31:42 +00:00
_LOGGER . warning ( err )
2018-08-29 15:04:04 +00:00
return True
2018-04-12 12:28:54 +00:00
async def async_unload_entry ( hass , entry ) :
""" Unload a config entry. """
2021-05-20 07:08:23 +00:00
unload_success = await hass . data [ DOMAIN ] [ entry . entry_id ] . async_reset ( )
2021-04-13 16:31:23 +00:00
if len ( hass . data [ DOMAIN ] ) == 0 :
hass . data . pop ( DOMAIN )
hass . services . async_remove ( DOMAIN , SERVICE_HUE_SCENE )
2021-05-20 07:08:23 +00:00
return unload_success
2021-04-13 16:31:23 +00:00
@core.callback
def _register_services ( hass ) :
""" Register Hue services. """
async def hue_activate_scene ( call , skip_reload = True ) :
""" Handle activation of Hue scene. """
# Get parameters
group_name = call . data [ ATTR_GROUP_NAME ]
scene_name = call . data [ ATTR_SCENE_NAME ]
# Call the set scene function on each bridge
tasks = [
bridge . hue_activate_scene (
2021-04-22 17:29:11 +00:00
call . data , skip_reload = skip_reload , hide_warnings = skip_reload
2021-04-13 16:31:23 +00:00
)
for bridge in hass . data [ DOMAIN ] . values ( )
if isinstance ( bridge , HueBridge )
]
results = await asyncio . gather ( * tasks )
# Did *any* bridge succeed? If not, refresh / retry
# Note that we'll get a "None" value for a successful call
if None not in results :
if skip_reload :
await hue_activate_scene ( call , skip_reload = False )
return
_LOGGER . warning (
" No bridge was able to activate " " scene %s in group %s " ,
scene_name ,
group_name ,
)
2021-05-20 07:08:23 +00:00
if not hass . services . has_service ( DOMAIN , SERVICE_HUE_SCENE ) :
2021-04-13 16:31:23 +00:00
# Register a local handler for scene activation
hass . services . async_register (
DOMAIN ,
SERVICE_HUE_SCENE ,
verify_domain_control ( hass , DOMAIN ) ( hue_activate_scene ) ,
schema = vol . Schema (
{
vol . Required ( ATTR_GROUP_NAME ) : cv . string ,
vol . Required ( ATTR_SCENE_NAME ) : cv . string ,
vol . Optional ( ATTR_TRANSITION ) : cv . positive_int ,
}
) ,
)