Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connection validation to config flow and add tests #103

Merged
merged 2 commits into from
Jan 8, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add connection validation to config flow
RobBie1221 committed Jan 8, 2022
commit 107778bf6b52c98ea3ba5bf8a74cc2a526761131
126 changes: 114 additions & 12 deletions custom_components/nefiteasy/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
"""Config flow for Nefit Easy Bosch Thermostat integration."""
from __future__ import annotations

import asyncio
import logging
from typing import Any

from homeassistant import config_entries
from aionefit import NefitCore
from homeassistant import config_entries, core, exceptions
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
import voluptuous as vol

from .const import ( # pylint:disable=unused-import
AUTH_ERROR_CREDENTIALS,
AUTH_ERROR_PASSWORD,
CONF_ACCESSKEY,
CONF_MAX_TEMP,
CONF_MIN_TEMP,
@@ -24,6 +28,85 @@
_LOGGER = logging.getLogger(__name__)


class NefitConnection:
"""Test the connection to NefitEasy and check read to verify description of messages."""

def __init__(self, serial_number: str, access_key: str, password: str) -> None:
"""Initialize."""
self.nefit = NefitCore(
serial_number=serial_number,
access_key=access_key,
password=password,
)

self.nefit.failed_auth_handler = self.failed_auth_handler
self.nefit.no_content_callback = self.no_content_callback
self.nefit.session_end_callback = self.session_end_callback

self.auth_failure = None

async def failed_auth_handler(self, event: str) -> None:
"""Report failed auth."""
self.nefit.xmppclient.connected_event.set()

if event == "auth_error_password":
self.auth_failure = AUTH_ERROR_PASSWORD
_LOGGER.debug("Invalid password for connecting to Bosch cloud.")
else:
self.auth_failure = AUTH_ERROR_CREDENTIALS
_LOGGER.debug(
"Invalid credentials (serial or accesskey) for connecting to Bosch cloud."
)

async def session_end_callback(self) -> None:
"""Session end."""

async def no_content_callback(self, data: Any) -> None:
"""No content."""

async def validate_connect(self) -> None:
"""Test if we can connect and communicate with the device."""

await self.nefit.connect()
try:
await asyncio.wait_for(
self.nefit.xmppclient.connected_event.wait(), timeout=10.0
)
except asyncio.TimeoutError as ex:
self.nefit.xmppclient.cancel_connection_attempt()
raise CannotConnect from ex

if self.auth_failure == AUTH_ERROR_CREDENTIALS:
raise InvalidCredentials

self.nefit.get("/gateway/brandID")
try:
await asyncio.wait_for(
self.nefit.xmppclient.message_event.wait(), timeout=10.0
)
except asyncio.TimeoutError as ex:
self.nefit.xmppclient.cancel_connection_attempt()
raise CannotCommunicate from ex

self.nefit.xmppclient.message_event.clear()

await self.nefit.disconnect()

if self.auth_failure == AUTH_ERROR_PASSWORD:
raise InvalidPassword


async def _validate_nefiteasy_connection(
hass: core.HomeAssistant, data: dict[str, Any]
) -> None:
"""Validate the user input allows us to connect."""
conn = NefitConnection(
data.get(CONF_SERIAL), data[CONF_ACCESSKEY], data[CONF_PASSWORD]
)

await conn.validate_connect()


class NefitEasyConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Nefit Easy Bosch Thermostat."""

@@ -46,11 +129,23 @@ async def async_step_user(
await self.async_set_unique_id(user_input[CONF_SERIAL])
self._abort_if_unique_id_configured()

self._serial = user_input[CONF_SERIAL]
self._accesskey = user_input[CONF_ACCESSKEY]
self._password = user_input[CONF_PASSWORD]
try:
await _validate_nefiteasy_connection(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except CannotCommunicate:
errors["base"] = "cannot_communicate"
except InvalidCredentials:
errors["base"] = "invalid_credentials"
except InvalidPassword:
errors["base"] = "invalid_password"

if not errors:
self._serial = user_input[CONF_SERIAL]
self._accesskey = user_input[CONF_ACCESSKEY]
self._password = user_input[CONF_PASSWORD]

return await self.async_step_options()
return await self.async_step_options()

schema = vol.Schema(
{
@@ -92,11 +187,18 @@ async def async_step_options(
step_id="options", data_schema=schema, errors=errors
)

async def async_step_import(self, import_config: ConfigType) -> FlowResult:
"""Handle the initial step."""
await self.async_set_unique_id(import_config[CONF_SERIAL])
self._abort_if_unique_id_configured()

return self.async_create_entry(
title=f"{import_config[CONF_SERIAL]}", data=import_config
)
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""


class CannotCommunicate(exceptions.HomeAssistantError):
"""Error to indicate we cannot communicate."""


class InvalidCredentials(exceptions.HomeAssistantError):
"""Error to indicate we cannot verify credentials."""


class InvalidPassword(exceptions.HomeAssistantError):
"""Error to indicate we cannot verify password."""
3 changes: 3 additions & 0 deletions custom_components/nefiteasy/const.py
Original file line number Diff line number Diff line change
@@ -33,6 +33,9 @@
STATE_INIT = "initializing"
STATE_ERROR_AUTH = "authentication_failed"

AUTH_ERROR_PASSWORD = "auth_error_password"
AUTH_ERROR_CREDENTIALS = "auth_error_credentials"

name = "name"
url = "url"
unit = "unit"
4 changes: 3 additions & 1 deletion custom_components/nefiteasy/strings.json
Original file line number Diff line number Diff line change
@@ -18,8 +18,10 @@
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"cannot_communicate": "Failed to communicate",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
"invalid_credentials": "Invalid serial number / access key combination",
"invalid_password": "Invalid password"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
5 changes: 4 additions & 1 deletion custom_components/nefiteasy/translations/en.json
Original file line number Diff line number Diff line change
@@ -5,8 +5,11 @@
},
"error": {
"cannot_connect": "Failed to connect",
"cannot_communicate": "Failed to communicate",
"invalid_auth": "Invalid authentication",
"unknown": "Unexpected error"
"unknown": "Unexpected error",
"invalid_credentials": "Invalid serial number / access key combination",
"invalid_password": "Invalid password"
},
"step": {
"options": {