def _host_is_same(host1: str, host2: str) -> bool: """Check if host1 and host2 are the same.""" host1 = host1.split(":")[0] host1 = host1 if is_ip_address(host1) else socket.gethostbyname(host1) host2 = host2.split(":")[0] host2 = host2 if is_ip_address(host2) else socket.gethostbyname(host2) return host1 == host2
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url(hass) if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ((allow_ip or not is_ip_address(str(external_url.host))) and (not require_current_request or external_url.host == _get_request_host()) and (not require_standard_port or external_url.is_default_port()) and (not require_ssl or (external_url.scheme == "https" and not is_ip_address(str(external_url.host))))): return normalize_url(str(external_url)) if allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url( hass, require_current_request=require_current_request) raise NoURLAvailableError
def _get_deprecated_base_url( hass: HomeAssistant, *, internal: bool = False, allow_ip: bool = True, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Work with the deprecated `base_url`, used as fallback.""" if hass.config.api is None or not hass.config.api.deprecated_base_url: raise NoURLAvailableError base_url = yarl.URL(hass.config.api.deprecated_base_url) # Rules that apply to both internal and external if ((allow_ip or not is_ip_address(str(base_url.host))) and (not require_current_request or base_url.host == _get_request_host()) and (not require_ssl or base_url.scheme == "https") and (not require_standard_port or base_url.is_default_port())): # Check to ensure an internal URL if internal and (str(base_url.host).endswith(".local") or (is_ip_address(str(base_url.host)) and not is_loopback(ip_address(base_url.host)) and is_private(ip_address(base_url.host)))): return normalize_url(str(base_url)) # Check to ensure an external URL (a little) if (not internal and not str(base_url.host).endswith(".local") and not (is_ip_address(str(base_url.host)) and is_local(ip_address(str(base_url.host))))): return normalize_url(str(base_url)) raise NoURLAvailableError
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: try: return _get_cloud_url(hass) except NoURLAvailableError: pass if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ( (allow_ip or not is_ip_address(str(external_url.host))) and ( not require_current_request or external_url.host == _get_request_host() ) and (not require_standard_port or external_url.is_default_port()) and ( not require_ssl or ( external_url.scheme == "https" and not is_ip_address(str(external_url.host)) ) ) ): return normalize_url(str(external_url)) try: return _get_deprecated_base_url( hass, allow_ip=allow_ip, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass if allow_cloud: try: return _get_cloud_url(hass, require_current_request=require_current_request) except NoURLAvailableError: pass # get ais url remote_access = hass.states.get("input_boolean.ais_remote_access").state if remote_access == "on": return hass.states.get("camera.remote_access").state else: return "http://" + hass.states.get("sensor.internal_ip_address").state + ":8180" raise NoURLAvailableError
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: try: return _get_cloud_url(hass) except NoURLAvailableError: pass if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ( (allow_ip or not is_ip_address(str(external_url.host))) and (not require_standard_port or external_url.is_default_port()) and ( not require_ssl or ( external_url.scheme == "https" and not is_ip_address(str(external_url.host)) ) ) ): return normalize_url(str(external_url)) try: return _get_deprecated_base_url( hass, allow_ip=allow_ip, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass if allow_cloud: try: return _get_cloud_url(hass) except NoURLAvailableError: pass raise NoURLAvailableError
def async_update_entry_from_discovery( hass: HomeAssistant, entry: config_entries.ConfigEntry, device: FluxLEDDiscovery, model_num: int | None, ) -> bool: """Update a config entry from a flux_led discovery.""" data_updates: dict[str, Any] = {} mac_address = device[ATTR_ID] assert mac_address is not None updates: dict[str, Any] = {} if not entry.unique_id: updates["unique_id"] = dr.format_mac(mac_address) if model_num and entry.data.get(CONF_MODEL_NUM) != model_num: data_updates[CONF_MODEL_NUM] = model_num async_populate_data_from_discovery(entry.data, data_updates, device) if is_ip_address(entry.title): updates["title"] = async_name_from_discovery(device) title_matches_name = entry.title == entry.data.get(CONF_NAME) if data_updates or title_matches_name: updates["data"] = {**entry.data, **data_updates} if title_matches_name: del updates["data"][CONF_NAME] if updates: return hass.config_entries.async_update_entry(entry, **updates) return False
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tesla Powerwall from a config entry.""" http_session = requests.Session() ip_address = entry.data[CONF_IP_ADDRESS] password = entry.data.get(CONF_PASSWORD) power_wall = Powerwall(ip_address, http_session=http_session) try: base_info = await hass.async_add_executor_job( _login_and_fetch_base_info, power_wall, ip_address, password) except PowerwallUnreachableError as err: http_session.close() raise ConfigEntryNotReady from err except MissingAttributeError as err: http_session.close() # The error might include some important information about what exactly changed. _LOGGER.error("The powerwall api has changed: %s", str(err)) persistent_notification.async_create(hass, API_CHANGED_ERROR_BODY, API_CHANGED_TITLE) return False except AccessDeniedError as err: _LOGGER.debug("Authentication failed", exc_info=err) http_session.close() raise ConfigEntryAuthFailed from err except APIError as err: http_session.close() raise ConfigEntryNotReady from err gateway_din = base_info.gateway_din if gateway_din and entry.unique_id is not None and is_ip_address( entry.unique_id): hass.config_entries.async_update_entry(entry, unique_id=gateway_din) runtime_data = PowerwallRuntimeData( api_changed=False, base_info=base_info, http_session=http_session, coordinator=None, ) manager = PowerwallDataManager(hass, power_wall, ip_address, password, runtime_data) coordinator = DataUpdateCoordinator( hass, _LOGGER, name="Powerwall site", update_method=manager.async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) await coordinator.async_config_entry_first_refresh() runtime_data[POWERWALL_COORDINATOR] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = runtime_data await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True
async def async_step_user(self, user_input: Optional[ConfigType] = None, error: Optional[str] = None): # pylint: disable=arguments-differ """Handle a flow initialized by the user.""" if user_input is not None: self._set_user_input(user_input) if not is_ip_address(self._host): return self.async_abort(reason="connection_error") ret = gateway.is_aqaragateway(self._host, self._password, self._model) if "error" in ret['status']: return self.async_abort(reason="connection_error") self._name = ret.get('name', '') self._model = ret.get('model', '') return self._async_get_entry() fields = OrderedDict() fields[vol.Required(CONF_HOST, default=self._host or vol.UNDEFINED)] = str fields[vol.Optional(CONF_PASSWORD, default=self._password or vol.UNDEFINED)] = str fields[vol.Optional(CONF_MODEL, default=self._model or ['m1s'])] = vol.In(OPT_DEVICE_NAME) return self.async_show_form(step_id="user", data_schema=vol.Schema(fields), errors={'base': error} if error else None)
async def async_step_init(self, user_input=None): """Manage options.""" if user_input is not None: if not is_ip_address(user_input.get(CONF_HOST)): return self.async_abort(reason="connection_error") self._host = user_input.get(CONF_HOST) if len(user_input.get(CONF_PASSWORD, "")) >= 1: self._password = user_input.get(CONF_PASSWORD) return self.async_create_entry( title='', data={ CONF_HOST: self._host, CONF_PASSWORD: self._password, CONF_MODEL: self._model }, ) self._host = self.config_entry.options[CONF_HOST] self._password = self.config_entry.options[CONF_PASSWORD] self._model = self.config_entry.options.get(CONF_MODEL, '') debug = self.config_entry.options.get(CONF_DEBUG, []) return self.async_show_form( step_id="init", data_schema=vol.Schema({ vol.Required(CONF_HOST, default=self._host): str, vol.Optional(CONF_PASSWORD, default=self._password): str, vol.Optional(CONF_MODEL): vol.In(OPT_DEVICE_NAME), vol.Optional(CONF_DEBUG, default=debug): cv.multi_select(OPT_DEBUG), }), )
def _get_internal_url( hass: HomeAssistant, *, allow_ip: bool = True, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get internal URL of this instance.""" if hass.config.internal_url: internal_url = yarl.URL(hass.config.internal_url) if ( (not require_current_request or internal_url.host == _get_request_host()) and (not require_ssl or internal_url.scheme == "https") and (not require_standard_port or internal_url.is_default_port()) and (allow_ip or not is_ip_address(str(internal_url.host))) ): return normalize_url(str(internal_url)) # Fallback to detected local IP if allow_ip and not ( require_ssl or hass.config.api is None or hass.config.api.use_ssl ): ip_url = yarl.URL.build( scheme="http", host=hass.config.api.local_ip, port=hass.config.api.port ) if ( not is_loopback(ip_address(ip_url.host)) and (not require_current_request or ip_url.host == _get_request_host()) and (not require_standard_port or ip_url.is_default_port()) ): return normalize_url(str(ip_url)) raise NoURLAvailableError
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: """Handle dhcp discovery.""" self.ip_address = discovery_info.ip gateway_din = discovery_info.hostname.upper() # The hostname is the gateway_din (unique_id) await self.async_set_unique_id(gateway_din) self._abort_if_unique_id_configured(updates={CONF_IP_ADDRESS: self.ip_address}) for entry in self._async_current_entries(include_ignore=False): if entry.data[CONF_IP_ADDRESS] == discovery_info.ip: if entry.unique_id is not None and is_ip_address(entry.unique_id): if self.hass.config_entries.async_update_entry( entry, unique_id=gateway_din ): self.hass.async_create_task( self.hass.config_entries.async_reload(entry.entry_id) ) return self.async_abort(reason="already_configured") self.context["title_placeholders"] = { "name": gateway_din, "ip_address": self.ip_address, } errors, info = await self._async_try_connect( {CONF_IP_ADDRESS: self.ip_address, CONF_PASSWORD: gateway_din[-5:]} ) if errors: if CONF_PASSWORD in errors: # The default password is the gateway din last 5 # if it does not work, we have to ask return await self.async_step_user() return self.async_abort(reason="cannot_connect") assert info is not None self.title = info["title"] return await self.async_step_confirm_discovery()
async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType) -> FlowResult: """Handle integration discovery.""" self._discovered_device = discovery_info mac = _async_unifi_mac_from_hass(discovery_info["hw_addr"]) await self.async_set_unique_id(mac) source_ip = discovery_info["source_ip"] direct_connect_domain = discovery_info["direct_connect_domain"] for entry in self._async_current_entries(): if entry.source == config_entries.SOURCE_IGNORE: if entry.unique_id == mac: return self.async_abort(reason="already_configured") continue entry_host = entry.data[CONF_HOST] entry_has_direct_connect = _host_is_direct_connect(entry_host) if entry.unique_id == mac: new_host = None if (entry_has_direct_connect and direct_connect_domain and entry_host != direct_connect_domain): new_host = direct_connect_domain elif (not entry_has_direct_connect and is_ip_address(entry_host) and entry_host != source_ip): new_host = source_ip if new_host: self.hass.config_entries.async_update_entry( entry, data={ **entry.data, CONF_HOST: new_host }) return self.async_abort(reason="already_configured") if entry_host in (direct_connect_domain, source_ip) or ( entry_has_direct_connect and (ip := await _async_resolve(self.hass, entry_host)) and ip == source_ip): return self.async_abort(reason="already_configured")
async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initialized by the user.""" errors = {} if user_input is not None: if not (host := user_input[CONF_HOST]): return await self.async_step_pick_device() if not is_ip_address(user_input[CONF_HOST]): errors["base"] = "no_ip" else: bulb = wizlight(host) try: bulbtype = await bulb.get_bulbtype() mac = await bulb.getMac() except WizLightTimeOutError: errors["base"] = "bulb_time_out" except ConnectionRefusedError: errors["base"] = "cannot_connect" except WizLightConnectionError: errors["base"] = "no_wiz_light" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: await self.async_set_unique_id(mac, raise_on_progress=False) self._abort_if_unique_id_configured( updates={CONF_HOST: user_input[CONF_HOST]} ) name = name_from_bulb_type_and_mac(bulbtype, mac) return self.async_create_entry( title=name, data=user_input, )
def async_update_entry_from_discovery( hass: HomeAssistant, entry: config_entries.ConfigEntry, device: FluxLEDDiscovery, model_num: int | None, allow_update_mac: bool, ) -> bool: """Update a config entry from a flux_led discovery.""" data_updates: dict[str, Any] = {} mac_address = device[ATTR_ID] assert mac_address is not None updates: dict[str, Any] = {} formatted_mac = dr.format_mac(mac_address) if not entry.unique_id or ( allow_update_mac and entry.unique_id != formatted_mac and mac_matches_by_one(formatted_mac, entry.unique_id) ): updates["unique_id"] = formatted_mac if model_num and entry.data.get(CONF_MODEL_NUM) != model_num: data_updates[CONF_MODEL_NUM] = model_num async_populate_data_from_discovery(entry.data, data_updates, device) if is_ip_address(entry.title): updates["title"] = async_name_from_discovery(device, model_num) title_matches_name = entry.title == entry.data.get(CONF_NAME) if data_updates or title_matches_name: updates["data"] = {**entry.data, **data_updates} if title_matches_name: del updates["data"][CONF_NAME] # If the title has changed and the config entry is loaded, a listener is # in place, and we should not reload if updates and not ("title" in updates and entry.state is ConfigEntryState.LOADED): return hass.config_entries.async_update_entry(entry, **updates) return False
async def async_step_init(self, user_input=None): """Manage options.""" if user_input is not None: if not is_ip_address(user_input.get(CONF_HOST)): return self.async_abort(reason="connection_error") self._host = user_input.get(CONF_HOST) if len(user_input.get(CONF_TOKEN, "")) >= 1: self._token = user_input.get(CONF_TOKEN) return self.async_create_entry( title='', data={ CONF_HOST: self._host, CONF_TOKEN: self._token, }, ) self._host = self.config_entry.options.get(CONF_HOST, '') self._token = self.config_entry.options.get(CONF_TOKEN, '') return self.async_show_form( step_id="init", data_schema=vol.Schema({ vol.Required(CONF_HOST, default=self._host): str, vol.Required(CONF_TOKEN, default=self._token): str, }), )
async def validate_host(hass: HomeAssistant, host: str) -> None: """Validate that the user input allows us to connect.""" if not is_ip_address(host): raise InvalidHost(f"Invalid IP address: {host}") client = Vallox(host) await client.get_info()
def _get_external_url( hass: HomeAssistant, *, allow_cloud: bool = True, allow_ip: bool = True, prefer_cloud: bool = False, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, ) -> str: """Get external URL of this instance.""" if prefer_cloud and allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url(hass) if hass.config.external_url: external_url = yarl.URL(hass.config.external_url) if ((allow_ip or not is_ip_address(str(external_url.host))) and (not require_current_request or external_url.host == _get_request_host()) and (not require_standard_port or external_url.is_default_port()) and (not require_ssl or (external_url.scheme == "https" and not is_ip_address(str(external_url.host))))): return normalize_url(str(external_url)) if allow_cloud: with suppress(NoURLAvailableError): return _get_cloud_url( hass, require_current_request=require_current_request) # get ais url remote_access = hass.states.get("input_boolean.ais_remote_access").state if remote_access == "on": import homeassistant.components.ais_dom.ais_global as ais_global return "https://" + ais_global.get_sercure_android_id_dom( ) + ".paczka.pro" else: return "http://" + hass.config.api.local_ip + ":" + str( hass.config.api.port) raise NoURLAvailableError
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Elk-M1 Control from a config entry.""" conf: MappingProxyType[str, Any] = entry.data host = urlparse(entry.data[CONF_HOST]).hostname _LOGGER.debug("Setting up elkm1 %s", conf["host"]) if not entry.unique_id or ":" not in entry.unique_id and is_ip_address( host): if device := await async_discover_device(hass, host): async_update_entry_from_discovery(hass, entry, device)
def test_is_ip_address(): """Test if strings are IP addresses.""" assert network_util.is_ip_address("192.168.0.1") assert network_util.is_ip_address("8.8.8.8") assert network_util.is_ip_address("::ffff:127.0.0.0") assert not network_util.is_ip_address("192.168.0.999") assert not network_util.is_ip_address("192.168.0.0/24") assert not network_util.is_ip_address("example.com")
async def async_step_import(self, user_input): """Handle import.""" _LOGGER.debug("Elk is importing from yaml") url = _make_url_from_data(user_input) if self._url_already_configured(url): return self.async_abort(reason="address_already_configured") host = urlparse(url).hostname _LOGGER.debug( "Importing is trying to fill unique id from discovery for %s", host) if is_ip_address(host) and (device := await async_discover_device( self.hass, host)): await self.async_set_unique_id(dr.format_mac(device.mac_address), raise_on_progress=False) self._abort_if_unique_id_configured()
async def async_step_init(self, user_input=None): """Manage options.""" if user_input is not None: if not is_ip_address(user_input.get(CONF_HOST)): return self.async_abort(reason="connection_error") self._host = user_input.get(CONF_HOST) if len(user_input.get(CONF_PASSWORD, "")) >= 1: self._password = user_input.get(CONF_PASSWORD) self._token = user_input.get(CONF_TOKEN, "") return self.async_create_entry( title='', data={ CONF_HOST: self._host, CONF_PASSWORD: self._password, CONF_TOKEN: self._token, CONF_MODEL: self._model, # CONF_STATS: user_input.get(CONF_STATS, False), CONF_DEBUG: user_input.get(CONF_DEBUG, []), CONF_NOFFLINE: user_input.get(CONF_NOFFLINE, True), }, ) self._host = self.config_entry.options[CONF_HOST] self._password = self.config_entry.options.get(CONF_PASSWORD, '') self._token = self.config_entry.options.get(CONF_TOKEN, '') self._model = self.config_entry.options.get(CONF_MODEL, '') # stats = self.config_entry.options.get(CONF_STATS, False) debug = self.config_entry.options.get(CONF_DEBUG, []) ignore_offline = self.config_entry.options.get(CONF_NOFFLINE, True) return self.async_show_form( step_id="init", data_schema=vol.Schema({ vol.Required(CONF_HOST, default=self._host): str, vol.Optional(CONF_PASSWORD, default=self._password): str, vol.Optional(CONF_TOKEN, default=self._token): str, # vol.Required(CONF_STATS, default=stats): bool, vol.Optional(CONF_DEBUG, default=debug): cv.multi_select(OPT_DEBUG), vol.Required(CONF_NOFFLINE, default=ignore_offline): bool, }), )
async def async_step_user(self, user_input: Optional[ConfigType] = None, error: Optional[str] = None): # pylint: disable=arguments-differ """Handle a flow initialized by the user.""" if user_input is not None: self._set_user_input(user_input) if not is_ip_address(self._host): return self.async_abort(reason="connection_error") if self._token and self._model in ('m1s', 'p3', 'h1', 'e1'): Utils.enable_telnet(self._host, self._token) if not self._check_port(23): return self.async_abort(reason="connection_error") ret = gateway.is_aqaragateway(self._host, self._password, self._model) if "error" in ret['status']: return self.async_abort(reason="connection_error") self._name = ret.get('name', '') # change to use long model name self._model = ret.get('model', '') if ret['token']: self._token = ret['token'] await self.async_set_unique_id(self._name) return self._async_get_entry() for name, _ in OPT_DEVICE_NAME.items(): if self._name and name in self._name.lower(): self._model = name break fields = OrderedDict() fields[vol.Required(CONF_HOST, default=self._host or vol.UNDEFINED)] = str fields[vol.Optional(CONF_PASSWORD, default=self._password or vol.UNDEFINED)] = str fields[vol.Optional(CONF_TOKEN, default=self._token or vol.UNDEFINED)] = str fields[vol.Optional(CONF_MODEL, default=self._model or 'm1s')] = vol.In(OPT_DEVICE_NAME) fields[vol.Optional(CONF_NOFFLINE, default=True)] = bool return self.async_show_form(step_id="user", data_schema=vol.Schema(fields), errors={'base': error} if error else None)
def async_update_entry_from_discovery( hass: HomeAssistant, entry: config_entries.ConfigEntry, device: Device30303, ) -> bool: """Update a config entry from a discovery.""" data_updates: dict[str, Any] = {} updates: dict[str, Any] = {} if not entry.unique_id: updates["unique_id"] = dr.format_mac(device.mac) if not entry.data.get(CONF_NAME) or is_ip_address(entry.data[CONF_NAME]): updates["title"] = data_updates[CONF_NAME] = device.name if not entry.data.get(CONF_MODEL) and "-" in device.hostname: data_updates[CONF_MODEL] = device.hostname.split("-", maxsplit=1)[0] if data_updates: updates["data"] = {**entry.data, **data_updates} if updates: return hass.config_entries.async_update_entry(entry, **updates) return False
async def async_step_user(self, user_input: Optional[ConfigType] = None, error: Optional[str] = None): # pylint: disable=arguments-differ """Handle a flow initialized by the user.""" if user_input is not None: self._set_user_input(user_input) if not is_ip_address(self._host): return self.async_abort(reason="connection_error") self._name = DEFAULT_NAME return self._async_get_entry() fields = OrderedDict() fields[vol.Required(CONF_HOST, default=self._host or vol.UNDEFINED)] = str fields[vol.Required(CONF_TOKEN, default=self._token or vol.UNDEFINED)] = str return self.async_show_form(step_id="user", data_schema=vol.Schema(fields), errors={'base': error} if error else None)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up RainMachine as config entry.""" hass.data.setdefault(DOMAIN, {DATA_CONTROLLER: {}, DATA_COORDINATOR: {}}) hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id] = {} websession = aiohttp_client.async_get_clientsession(hass) client = Client(session=websession) try: await client.load_local( entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], port=entry.data[CONF_PORT], ssl=entry.data.get(CONF_SSL, DEFAULT_SSL), ) except RainMachineError as err: raise ConfigEntryNotReady from err # regenmaschine can load multiple controllers at once, but we only grab the one # we loaded above: controller = hass.data[DOMAIN][DATA_CONTROLLER][ entry.entry_id] = get_client_controller(client) entry_updates = {} if not entry.unique_id or is_ip_address(entry.unique_id): # If the config entry doesn't already have a unique ID, set one: entry_updates["unique_id"] = controller.mac if CONF_ZONE_RUN_TIME in entry.data: # If a zone run time exists in the config entry's data, pop it and move it to # options: data = {**entry.data} entry_updates["data"] = data entry_updates["options"] = { **entry.options, CONF_ZONE_RUN_TIME: data.pop(CONF_ZONE_RUN_TIME), } if entry_updates: hass.config_entries.async_update_entry(entry, **entry_updates) async def async_update(api_category: str) -> dict: """Update the appropriate API data based on a category.""" try: if api_category == DATA_PROGRAMS: return await controller.programs.all(include_inactive=True) if api_category == DATA_PROVISION_SETTINGS: return await controller.provisioning.settings() if api_category == DATA_RESTRICTIONS_CURRENT: return await controller.restrictions.current() if api_category == DATA_RESTRICTIONS_UNIVERSAL: return await controller.restrictions.universal() return await controller.zones.all(details=True, include_inactive=True) except RainMachineError as err: raise UpdateFailed(err) from err controller_init_tasks = [] for api_category in [ DATA_PROGRAMS, DATA_PROVISION_SETTINGS, DATA_RESTRICTIONS_CURRENT, DATA_RESTRICTIONS_UNIVERSAL, DATA_ZONES, ]: coordinator = hass.data[DOMAIN][DATA_COORDINATOR][ entry.entry_id][api_category] = DataUpdateCoordinator( hass, LOGGER, name=f'{controller.name} ("{api_category}")', update_interval=DEFAULT_UPDATE_INTERVAL, update_method=partial(async_update, api_category), ) controller_init_tasks.append(coordinator.async_refresh()) await asyncio.gather(*controller_init_tasks) hass.config_entries.async_setup_platforms(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(async_reload_entry)) return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up RainMachine as config entry.""" websession = aiohttp_client.async_get_clientsession(hass) client = Client(session=websession) try: await client.load_local( entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], port=entry.data[CONF_PORT], ssl=entry.data.get(CONF_SSL, DEFAULT_SSL), ) except RainMachineError as err: raise ConfigEntryNotReady from err # regenmaschine can load multiple controllers at once, but we only grab the one # we loaded above: controller = get_client_controller(client) entry_updates: dict[str, Any] = {} if not entry.unique_id or is_ip_address(entry.unique_id): # If the config entry doesn't already have a unique ID, set one: entry_updates["unique_id"] = controller.mac if CONF_ZONE_RUN_TIME in entry.data: # If a zone run time exists in the config entry's data, pop it and move it to # options: data = {**entry.data} entry_updates["data"] = data entry_updates["options"] = { **entry.options, CONF_ZONE_RUN_TIME: data.pop(CONF_ZONE_RUN_TIME), } if entry_updates: hass.config_entries.async_update_entry(entry, **entry_updates) async def async_update(api_category: str) -> dict: """Update the appropriate API data based on a category.""" data: dict = {} try: if api_category == DATA_PROGRAMS: data = await controller.programs.all(include_inactive=True) elif api_category == DATA_PROVISION_SETTINGS: data = await controller.provisioning.settings() elif api_category == DATA_RESTRICTIONS_CURRENT: data = await controller.restrictions.current() elif api_category == DATA_RESTRICTIONS_UNIVERSAL: data = await controller.restrictions.universal() else: data = await controller.zones.all(details=True, include_inactive=True) except RainMachineError as err: raise UpdateFailed(err) from err return data controller_init_tasks = [] coordinators = {} for api_category in ( DATA_PROGRAMS, DATA_PROVISION_SETTINGS, DATA_RESTRICTIONS_CURRENT, DATA_RESTRICTIONS_UNIVERSAL, DATA_ZONES, ): coordinator = coordinators[api_category] = DataUpdateCoordinator( hass, LOGGER, name=f'{controller.name} ("{api_category}")', update_interval=UPDATE_INTERVALS[api_category], update_method=partial(async_update, api_category), ) controller_init_tasks.append(coordinator.async_refresh()) await asyncio.gather(*controller_init_tasks) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { DATA_CONTROLLER: controller, DATA_COORDINATOR: coordinators, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(async_reload_entry)) async def async_pause_watering(call: ServiceCall) -> None: """Pause watering for a set number of seconds.""" controller = async_get_controller_for_service_call(hass, call) await controller.watering.pause_all(call.data[CONF_SECONDS]) await async_update_programs_and_zones(hass, entry) async def async_push_weather_data(call: ServiceCall) -> None: """Push weather data to the device.""" controller = async_get_controller_for_service_call(hass, call) await controller.parsers.post_data( { CONF_WEATHER: [ { key: value for key, value in call.data.items() if key != CONF_DEVICE_ID } ] } ) async def async_stop_all(call: ServiceCall) -> None: """Stop all watering.""" controller = async_get_controller_for_service_call(hass, call) await controller.watering.stop_all() await async_update_programs_and_zones(hass, entry) async def async_unpause_watering(call: ServiceCall) -> None: """Unpause watering.""" controller = async_get_controller_for_service_call(hass, call) await controller.watering.unpause_all() await async_update_programs_and_zones(hass, entry) for service_name, schema, method in ( ( SERVICE_NAME_PAUSE_WATERING, SERVICE_PAUSE_WATERING_SCHEMA, async_pause_watering, ), ( SERVICE_NAME_PUSH_WEATHER_DATA, SERVICE_PUSH_WEATHER_DATA_SCHEMA, async_push_weather_data, ), (SERVICE_NAME_STOP_ALL, SERVICE_SCHEMA, async_stop_all), (SERVICE_NAME_UNPAUSE_WATERING, SERVICE_SCHEMA, async_unpause_watering), ): if hass.services.has_service(DOMAIN, service_name): continue hass.services.async_register(DOMAIN, service_name, method, schema=schema) return True
def get_url( hass: HomeAssistant, *, require_current_request: bool = False, require_ssl: bool = False, require_standard_port: bool = False, allow_internal: bool = True, allow_external: bool = True, allow_cloud: bool = True, allow_ip: bool = True, prefer_external: bool = False, prefer_cloud: bool = False, ) -> str: """Get a URL to this instance.""" if require_current_request and http.current_request.get() is None: raise NoURLAvailableError order = [TYPE_URL_INTERNAL, TYPE_URL_EXTERNAL] if prefer_external: order.reverse() # Try finding an URL in the order specified for url_type in order: if allow_internal and url_type == TYPE_URL_INTERNAL: try: return _get_internal_url( hass, allow_ip=allow_ip, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass if allow_external and url_type == TYPE_URL_EXTERNAL: try: return _get_external_url( hass, allow_cloud=allow_cloud, allow_ip=allow_ip, prefer_cloud=prefer_cloud, require_current_request=require_current_request, require_ssl=require_ssl, require_standard_port=require_standard_port, ) except NoURLAvailableError: pass # For current request, we accept loopback interfaces (e.g., 127.0.0.1), # the Supervisor hostname and localhost transparently request_host = _get_request_host() if ( require_current_request and request_host is not None and hass.config.api is not None ): scheme = "https" if hass.config.api.use_ssl else "http" current_url = yarl.URL.build( scheme=scheme, host=request_host, port=hass.config.api.port ) known_hostnames = ["localhost"] if hass.components.hassio.is_hassio(): host_info = hass.components.hassio.get_host_info() known_hostnames.extend( [host_info["hostname"], f"{host_info['hostname']}.local"] ) if ( ( ( allow_ip and is_ip_address(request_host) and is_loopback(ip_address(request_host)) ) or request_host in known_hostnames ) and (not require_ssl or current_url.scheme == "https") and (not require_standard_port or current_url.is_default_port()) ): return normalize_url(str(current_url)) # We have to be honest now, we have no viable option available raise NoURLAvailableError