Skip to content

Commit

Permalink
Merge pull request #19 from vaproloff/dev
Browse files Browse the repository at this point in the history
Added reauth config flow
  • Loading branch information
vaproloff authored Nov 12, 2024
2 parents 4062546 + 62d3185 commit 5d76926
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 76 deletions.
19 changes: 10 additions & 9 deletions custom_components/tion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CONF_USERNAME,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_create_clientsession
import homeassistant.helpers.config_validation as cv
Expand Down Expand Up @@ -39,7 +40,7 @@
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.time_period,
): cv.positive_timedelta,
vol.Optional(CONF_FILE_PATH, default=DEFAULT_AUTH_FILENAME): cv.string,
}
)
Expand Down Expand Up @@ -102,11 +103,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
if hass.data.get(DOMAIN) is None:
hass.data.setdefault(DOMAIN, {})

if entry.options:
entry_data = dict(entry.data)
entry_data.update(entry.options)
hass.config_entries.async_update_entry(entry, data=entry_data, options={})

async def update_auth_data(**kwargs):
hass.config_entries.async_update_entry(entry, data=kwargs)

Expand All @@ -115,13 +111,18 @@ async def update_auth_data(**kwargs):
session,
username=entry.data.get(CONF_USERNAME),
password=entry.data.get(CONF_PASSWORD),
min_update_interval_sec=entry.data.get(CONF_SCAN_INTERVAL),
min_update_interval_sec=entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
auth=entry.data.get(AUTH_DATA),
)
client.add_update_listener(update_auth_data)

assert await client.authorization, "Couldn't get authorisation data!"
_LOGGER.info("Api initialized with authorization %s", await client.authorization)
auth_data = await client.authorization
if not auth_data:
raise ConfigEntryAuthFailed("Couldn't get authorisation data.")

_LOGGER.info("Api initialized with authorization %s", auth_data)

hass.data[DOMAIN][entry.entry_id] = client

Expand Down
68 changes: 34 additions & 34 deletions custom_components/tion/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ async def _headers(self):
"Accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "ru-RU",
"Authorization": await self.authorization,
"Authorization": self._authorization,
"Connection": "Keep-Alive",
"Content-Type": "application/json",
"Host": "api2.magicair.tion.ru",
Expand All @@ -170,9 +170,10 @@ async def authorization(self) -> str:
if await self._get_authorization():
return self._authorization

return None
elif await self._get_data():
return self._authorization

return self._authorization
return None

def add_update_listener(self, coro):
"""Add entry data update listener function."""
Expand Down Expand Up @@ -214,50 +215,49 @@ async def _get_authorization(self):
)
return False

async def get_location_data(self, force=False) -> bool:
"""Get locations data from Tion API."""
async def _get_data(self):
response = await self._session.get(
url=f"{self._API_ENDPOINT}{self._LOCATION_URL}",
headers=await self._headers,
timeout=10,
)

async def get_data():
response = await self._session.get(
url=f"{self._API_ENDPOINT}{self._LOCATION_URL}",
headers=await self._headers,
timeout=10,
if response.status == 200:
self._locations = [
TionLocation(location) for location in await response.json()
]
self._last_update = time()

_LOGGER.debug(
"TionClient: location data has been updated (%s)", self._last_update
)
return True

if response.status == 200:
self._locations = [
TionLocation(location) for location in await response.json()
]
self._last_update = time()
if response.status == 401:
_LOGGER.info("TionClient: need to get new authorization")
if await self._get_authorization():
return await self._get_data()

_LOGGER.debug(
"TionClient: location data has been updated (%s)", self._last_update
)
return True

if response.status == 401:
_LOGGER.info("TionClient: need to get new authorization")
if await self._get_authorization():
return await get_data()

_LOGGER.error("TionClient: authorization failed!")
else:
_LOGGER.error(
"TionClient: response while getting location data: status code: %s, content:\n%s",
response.status,
await response.json(),
)
_LOGGER.error("TionClient: authorization failed!")
else:
_LOGGER.error(
"TionClient: response while getting location data: status code: %s, content:\n%s",
response.status,
await response.json(),
)

return False
return False

async def get_location_data(self, force=False) -> bool:
"""Get locations data from Tion API."""
async with self._temp_lock:
if not force and (time() - self._last_update) < self._min_update_interval:
_LOGGER.debug(
"TionClient: location data already updated recently. Skipping request"
)
return self._locations is not None

return await get_data()
return await self._get_data()

async def get_zone(self, guid: str, force=False) -> TionZone | None:
"""Get zone data by guid from Tion API."""
Expand Down
62 changes: 44 additions & 18 deletions custom_components/tion/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Adds config flow (UI flow) for Tion component."""

from collections.abc import Mapping
import hashlib
import logging
from typing import Any
Expand Down Expand Up @@ -54,17 +55,10 @@ async def async_step_user(
if user_input is not None:
self._async_abort_entries_match({CONF_USERNAME: user_input[CONF_USERNAME]})

try:
interval = int(user_input.get(CONF_SCAN_INTERVAL))
except ValueError:
interval = DEFAULT_SCAN_INTERVAL
except TypeError:
interval = DEFAULT_SCAN_INTERVAL

auth_data = await self._get_auth_data(
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
interval,
DEFAULT_SCAN_INTERVAL,
)

if auth_data is None:
Expand All @@ -74,7 +68,6 @@ async def async_step_user(
sha256_hash.update(user_input[CONF_USERNAME].encode())
unique_id = f"{sha256_hash.hexdigest()}"

# Checks that the device is actually unique, otherwise abort
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()

Expand All @@ -83,7 +76,6 @@ async def async_step_user(
data={
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_SCAN_INTERVAL: interval,
AUTH_DATA: auth_data,
},
)
Expand All @@ -94,9 +86,6 @@ async def async_step_user(
{
vol.Required(CONF_USERNAME, default=""): str,
vol.Required(CONF_PASSWORD, default=""): str,
vol.Required(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): vol.Coerce(int),
}
),
errors=errors,
Expand All @@ -111,6 +100,47 @@ async def async_step_import(
)
return await self.async_step_user(import_config)

async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Dialog that informs the user that reauth is required."""
errors: dict[str, str] = {}
if user_input is not None:
auth_data = await self._get_auth_data(
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
DEFAULT_SCAN_INTERVAL,
)

if auth_data is None:
errors["base"] = "invalid_auth"
else:
return self.async_create_entry(
title=user_input[CONF_USERNAME],
data={
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
AUTH_DATA: auth_data,
},
)

return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Required(CONF_USERNAME, default=""): str,
vol.Required(CONF_PASSWORD, default=""): str,
}
),
errors=errors,
)


class TionOptionsFlow(OptionsFlow):
"""Tion options flow handler."""
Expand All @@ -129,13 +159,9 @@ async def async_step_init(self, user_input=None):
step_id="init",
data_schema=vol.Schema(
{
vol.Required(
CONF_PASSWORD,
default=self.config_entry.data.get(CONF_PASSWORD),
): str,
vol.Required(
CONF_SCAN_INTERVAL,
default=self.config_entry.data.get(CONF_SCAN_INTERVAL),
default=self.config_entry.options.get(CONF_SCAN_INTERVAL),
): vol.Coerce(int),
}
),
Expand Down
3 changes: 1 addition & 2 deletions custom_components/tion/const.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"""Constant variables used by integration."""

from datetime import timedelta
from enum import StrEnum

from homeassistant.const import Platform

DOMAIN = "tion"
DEFAULT_AUTH_FILENAME = "tion_auth"
DEFAULT_SCAN_INTERVAL = timedelta(minutes=1)
DEFAULT_SCAN_INTERVAL = 60
AUTH_DATA = "auth"
MANUFACTURER = "Tion"
PLATFORMS = [
Expand Down
2 changes: 1 addition & 1 deletion custom_components/tion/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/vaproloff/tion_home_assistant/issues",
"requirements": [],
"version": "2024.11.2"
"version": "2024.11.3"
}
7 changes: 1 addition & 6 deletions custom_components/tion/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@
"data": {
"name": "[%key:common::config_flow::data::name%]",
"username": "Username",
"password": "Password",
"scan_interval": "API poll interval"
},
"data_description": {
"scan_interval": "Tion devices data minimum update period."
"password": "Password"
}
}
},
Expand All @@ -28,7 +24,6 @@
"step": {
"init": {
"data": {
"password": "Password",
"scan_interval": "API poll interval"
},
"data_description": {
Expand Down
7 changes: 1 addition & 6 deletions custom_components/tion/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@
"description": "Введите данные учётной записи Tion",
"data": {
"username": "Логин",
"password": "Пароль",
"scan_interval": "Интервал обновления"
},
"data_description": {
"scan_interval": "Минимальный период обновления данных устройств Tion от API."
"password": "Пароль"
}
}
},
Expand All @@ -26,7 +22,6 @@
"step": {
"init": {
"data": {
"password": "Пароль",
"scan_interval": "Интервал обновления"
},
"data_description": {
Expand Down

0 comments on commit 5d76926

Please sign in to comment.