async def async_step_hassio( self, discovery_info: HassioServiceInfo) -> FlowResult: """Prepare configuration for a Hass.io deCONZ bridge. This flow is triggered by the discovery component. """ LOGGER.debug("deCONZ HASSIO discovery %s", pformat(discovery_info.config)) self.bridge_id = normalize_bridge_id( discovery_info.config[CONF_SERIAL]) await self.async_set_unique_id(self.bridge_id) self.host = discovery_info.config[CONF_HOST] self.port = discovery_info.config[CONF_PORT] self.api_key = discovery_info.config[CONF_API_KEY] self._abort_if_unique_id_configured( updates={ CONF_HOST: self.host, CONF_PORT: self.port, CONF_API_KEY: self.api_key, }) self.context["configuration_url"] = HASSIO_CONFIGURATION_URL self._hassio_discovery = discovery_info.config return await self.async_step_hassio_confirm()
async def async_step_ssdp(self, discovery_info): """Handle a discovered deCONZ bridge.""" if (discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) != DECONZ_MANUFACTURERURL): return self.async_abort(reason="not_deconz_bridge") LOGGER.debug("deCONZ SSDP discovery %s", pformat(discovery_info)) self.bridge_id = normalize_bridge_id( discovery_info[ssdp.ATTR_UPNP_SERIAL]) parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]) entry = await self.async_set_unique_id(self.bridge_id) if entry and entry.source == "hassio": return self.async_abort(reason="already_configured") self._abort_if_unique_id_configured(updates={ CONF_HOST: parsed_url.hostname, CONF_PORT: parsed_url.port }) self.context["title_placeholders"] = {"host": parsed_url.hostname} self.deconz_config = { CONF_HOST: parsed_url.hostname, CONF_PORT: parsed_url.port, } return await self.async_step_link()
async def async_call_deconz_service(service_call: ServiceCall) -> None: """Call correct deCONZ service.""" service = service_call.service service_data = service_call.data if CONF_BRIDGE_ID in service_data: found_gateway = False bridge_id = normalize_bridge_id(service_data[CONF_BRIDGE_ID]) for possible_gateway in hass.data[DOMAIN].values(): if possible_gateway.bridgeid == bridge_id: gateway = possible_gateway found_gateway = True break if not found_gateway: LOGGER.error("Could not find the gateway %s", bridge_id) return else: try: gateway = get_master_gateway(hass) except ValueError: LOGGER.error("No master gateway available") return if service == SERVICE_CONFIGURE_DEVICE: await async_configure_service(gateway, service_data) elif service == SERVICE_DEVICE_REFRESH: await async_refresh_devices_service(gateway) elif service == SERVICE_REMOVE_ORPHANED_ENTRIES: await async_remove_orphaned_entries_service(gateway)
async def async_step_ssdp( self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: """Handle a discovered deCONZ bridge.""" LOGGER.debug("deCONZ SSDP discovery %s", pformat(discovery_info)) self.bridge_id = normalize_bridge_id( discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]) parsed_url = urlparse(discovery_info.ssdp_location) entry = await self.async_set_unique_id(self.bridge_id) if entry and entry.source == config_entries.SOURCE_HASSIO: return self.async_abort(reason="already_configured") self.host = cast(str, parsed_url.hostname) self.port = cast(int, parsed_url.port) self._abort_if_unique_id_configured(updates={ CONF_HOST: self.host, CONF_PORT: self.port, }) self.context.update({ "title_placeholders": { "host": self.host }, "configuration_url": f"http://{self.host}:{self.port}", }) return await self.async_step_link()
async def async_step_ssdp(self, discovery_info): """Handle a discovered deCONZ bridge.""" if ( discovery_info.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) != DECONZ_MANUFACTURERURL ): return self.async_abort(reason="not_deconz_bridge") self.bridge_id = normalize_bridge_id(discovery_info[ssdp.ATTR_UPNP_SERIAL]) parsed_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION]) for entry in self.hass.config_entries.async_entries(DOMAIN): if self.bridge_id == entry.unique_id: if entry.source == "hassio": return self.async_abort(reason="already_configured") return self._update_entry(entry, parsed_url.hostname, parsed_url.port) await self.async_set_unique_id(self.bridge_id) # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = {"host": parsed_url.hostname} self.deconz_config = { CONF_HOST: parsed_url.hostname, CONF_PORT: parsed_url.port, } return await self.async_step_link()
async def async_step_ssdp( self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult: """Handle a discovered deCONZ bridge.""" if (discovery_info.upnp.get(ssdp.ATTR_UPNP_MANUFACTURER_URL) != DECONZ_MANUFACTURERURL): return self.async_abort(reason="not_deconz_bridge") LOGGER.debug("deCONZ SSDP discovery %s", pformat(discovery_info)) self.bridge_id = normalize_bridge_id( discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL]) parsed_url = urlparse(discovery_info.ssdp_location) entry = await self.async_set_unique_id(self.bridge_id) if entry and entry.source == config_entries.SOURCE_HASSIO: return self.async_abort(reason="already_configured") hostname = cast(str, parsed_url.hostname) port = cast(int, parsed_url.port) self._abort_if_unique_id_configured(updates={ CONF_HOST: hostname, CONF_PORT: port }) self.context["title_placeholders"] = {"host": hostname} self.deconz_config = {CONF_HOST: hostname, CONF_PORT: port} return await self.async_step_link()
async def async_configure_service(hass, data): """Set attribute of device in deCONZ. 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. 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/ """ gateway = get_master_gateway(hass) if CONF_BRIDGE_ID in data: gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] field = data.get(SERVICE_FIELD, "") entity_id = data.get(SERVICE_ENTITY) data = data[SERVICE_DATA] if entity_id: try: field = gateway.deconz_ids[entity_id] + field except KeyError: LOGGER.error("Could not find the entity %s", entity_id) return await gateway.api.request("put", field, json=data)
async def async_remove_orphaned_entries_service(hass, data): """Remove orphaned deCONZ entries from device and entity registries.""" gateway = get_master_gateway(hass) if CONF_BRIDGE_ID in data: gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] entity_registry = await hass.helpers.entity_registry.async_get_registry() device_registry = await hass.helpers.device_registry.async_get_registry() entity_entries = async_entries_for_config_entry( entity_registry, gateway.config_entry.entry_id) entities_to_be_removed = [] devices_to_be_removed = [ entry.id for entry in device_registry.devices.values() if gateway.config_entry.entry_id in entry.config_entries ] # Don't remove the Gateway host entry gateway_host = device_registry.async_get_device( connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)}, identifiers=set(), ) if gateway_host.id in devices_to_be_removed: devices_to_be_removed.remove(gateway_host.id) # Don't remove the Gateway service entry gateway_service = device_registry.async_get_device(identifiers={ (DOMAIN, gateway.api.config.bridgeid) }, connections=set()) if gateway_service.id in devices_to_be_removed: devices_to_be_removed.remove(gateway_service.id) # Don't remove devices belonging to available events for event in gateway.events: if event.device_id in devices_to_be_removed: devices_to_be_removed.remove(event.device_id) for entry in entity_entries: # Don't remove available entities if entry.unique_id in gateway.entities[entry.domain]: # Don't remove devices with available entities if entry.device_id in devices_to_be_removed: devices_to_be_removed.remove(entry.device_id) continue # Remove entities that are not available entities_to_be_removed.append(entry.entity_id) # Remove unavailable entities for entity_id in entities_to_be_removed: entity_registry.async_remove(entity_id) # Remove devices that don't belong to any entity for device_id in devices_to_be_removed: if len(async_entries_for_device(entity_registry, device_id)) == 0: device_registry.async_remove_device(device_id)
async def async_refresh_devices_service(hass, data): """Refresh available devices from deCONZ.""" gateway = get_master_gateway(hass) if CONF_BRIDGE_ID in data: gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] gateway.ignore_state_updates = True await gateway.api.refresh_state() gateway.ignore_state_updates = False gateway.async_add_device_callback(NEW_GROUP) gateway.async_add_device_callback(NEW_LIGHT) gateway.async_add_device_callback(NEW_SCENE) gateway.async_add_device_callback(NEW_SENSOR)
async def async_refresh_devices_service(hass, data): """Refresh available devices from deCONZ.""" gateway = get_master_gateway(hass) if CONF_BRIDGE_ID in data: gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] groups = set(gateway.api.groups.keys()) lights = set(gateway.api.lights.keys()) scenes = set(gateway.api.scenes.keys()) sensors = set(gateway.api.sensors.keys()) await gateway.api.refresh_state(ignore_update=True) gateway.async_add_device_callback( NEW_GROUP, [ group for group_id, group in gateway.api.groups.items() if group_id not in groups ], ) gateway.async_add_device_callback( NEW_LIGHT, [ light for light_id, light in gateway.api.lights.items() if light_id not in lights ], ) gateway.async_add_device_callback( NEW_SCENE, [ scene for scene_id, scene in gateway.api.scenes.items() if scene_id not in scenes ], ) gateway.async_add_device_callback( NEW_SENSOR, [ sensor for sensor_id, sensor in gateway.api.sensors.items() if sensor_id not in sensors ], )
async def async_step_hassio(self, user_input=None): """Prepare configuration for a Hass.io deCONZ bridge. This flow is triggered by the discovery component. """ self.bridge_id = normalize_bridge_id(user_input[CONF_SERIAL]) await self.async_set_unique_id(self.bridge_id) self._abort_if_unique_id_configured( updates={ CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT], CONF_API_KEY: user_input[CONF_API_KEY], } ) self._hassio_discovery = user_input return await self.async_step_hassio_confirm()
async def async_step_hassio(self, discovery_info): """Prepare configuration for a Hass.io deCONZ bridge. This flow is triggered by the discovery component. """ LOGGER.debug("deCONZ HASSIO discovery %s", pformat(discovery_info)) self.bridge_id = normalize_bridge_id(discovery_info[CONF_SERIAL]) await self.async_set_unique_id(self.bridge_id) self._abort_if_unique_id_configured( updates={ CONF_HOST: discovery_info[CONF_HOST], CONF_PORT: discovery_info[CONF_PORT], CONF_API_KEY: discovery_info[CONF_API_KEY], }) self._hassio_discovery = discovery_info return await self.async_step_hassio_confirm()
async def async_step_hassio(self, user_input=None): """Prepare configuration for a Hass.io deCONZ bridge. This flow is triggered by the discovery component. """ self.bridge_id = normalize_bridge_id(user_input[CONF_SERIAL]) for entry in self.hass.config_entries.async_entries(DOMAIN): if self.bridge_id == entry.unique_id: return self._update_entry( entry, user_input[CONF_HOST], user_input[CONF_PORT], user_input[CONF_API_KEY], ) await self.async_set_unique_id(self.bridge_id) self._hassio_discovery = user_input return await self.async_step_hassio_confirm()
async def async_step_hassio(self, user_input=None): """Prepare configuration for a Hass.io deCONZ bridge. This flow is triggered by the discovery component. """ self.bridge_id = normalize_bridge_id(user_input[CONF_SERIAL]) gateway = self.hass.data.get(DOMAIN, {}).get(self.bridge_id) if gateway: return self._update_entry( gateway.config_entry, user_input[CONF_HOST], user_input[CONF_PORT], user_input[CONF_API_KEY], ) await self.async_set_unique_id(self.bridge_id) self._hassio_discovery = user_input return await self.async_step_hassio_confirm()