Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
    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)
Esempio n. 9
0
    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),
            }),
        )
Esempio n. 10
0
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()
Esempio n. 12
0
 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")
Esempio n. 13
0
 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,
                 )
Esempio n. 14
0
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
Esempio n. 15
0
    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,
            }),
        )
Esempio n. 16
0
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()
Esempio n. 17
0
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
Esempio n. 18
0
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)
Esempio n. 19
0
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")
Esempio n. 20
0
    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()
Esempio n. 21
0
    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,
            }),
        )
Esempio n. 22
0
    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)
Esempio n. 23
0
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
Esempio n. 24
0
    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)
Esempio n. 25
0
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
Esempio n. 26
0
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
Esempio n. 27
0
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