From 7f9010a00a4ee72ab5f02e21dd5482481de22dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=A8=D1=83=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Thu, 25 Jan 2024 17:50:22 +0300 Subject: [PATCH 1/2] config flow: pass --- custom_components/zont_ha/__init__.py | 25 ++-- custom_components/zont_ha/config_flow.py | 115 ++++++++++++++++-- custom_components/zont_ha/const.py | 4 +- custom_components/zont_ha/core/models_zont.py | 8 ++ custom_components/zont_ha/core/zont.py | 2 +- .../zont_ha/translations/en.json | 15 ++- 6 files changed, 140 insertions(+), 29 deletions(-) diff --git a/custom_components/zont_ha/__init__.py b/custom_components/zont_ha/__init__.py index 6fdbcb4..0e0e224 100644 --- a/custom_components/zont_ha/__init__.py +++ b/custom_components/zont_ha/__init__.py @@ -16,18 +16,19 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry) -> bool: - entry_id = config_entry.entry_id - email = config_entry.data.get("mail") - token = config_entry.data.get("token") - zont = Zont(hass, email, token) - coordinator = ZontCoordinator(hass, zont) - await coordinator.async_config_entry_first_refresh() - - hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][entry_id] = coordinator - await hass.config_entries.async_forward_entry_setups( - config_entry, PLATFORMS - ) + _LOGGER.debug(f'config entry: {config_entry.data}') + # entry_id = config_entry.entry_id + # email = config_entry.data.get("mail") + # token = config_entry.data.get("token") + # zont = Zont(hass, email, token) + # coordinator = ZontCoordinator(hass, zont) + # await coordinator.async_config_entry_first_refresh() + # + # hass.data.setdefault(DOMAIN, {}) + # hass.data[DOMAIN][entry_id] = coordinator + # await hass.config_entries.async_forward_entry_setups( + # config_entry, PLATFORMS + # ) return True diff --git a/custom_components/zont_ha/config_flow.py b/custom_components/zont_ha/config_flow.py index 7987e10..d40ecc3 100644 --- a/custom_components/zont_ha/config_flow.py +++ b/custom_components/zont_ha/config_flow.py @@ -1,3 +1,4 @@ +import base64 import logging import re from http import HTTPStatus @@ -6,14 +7,39 @@ from homeassistant import config_entries from homeassistant.core import HomeAssistant -from .const import DOMAIN +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from .const import DOMAIN, URL_GET_TOKEN from .core.exceptions import RequestAPIZONTError, InvalidMail +from .core.models_zont import ErrorZont, TokenZont from .core.zont import Zont _LOGGER = logging.getLogger(__name__) -async def validate_auth(hass: HomeAssistant, mail: str, token: str) -> None: +async def get_token( + hass: HomeAssistant, mail: str, login: str, password: str +) -> str: + session = async_get_clientsession(hass) + basic = f'{login}:{password}'.encode("utf-8") + basic = base64.b64decode(basic).decode() + headers = { + 'Authorization': f'Basic {basic}', + 'X-ZONT-Client': mail, + 'Content-Type': 'application/json' + } + response = await session.post(url=URL_GET_TOKEN, headers=headers) + text = await response.text() + status_code = response.status + if status_code != HTTPStatus.OK: + error = ErrorZont.parse_raw(text) + raise RequestAPIZONTError(error) + data = TokenZont.parse_raw(text) + return data.token + + +async def validate_auth_token( + hass: HomeAssistant, mail: str, token: str +) -> None: """Валидация токена zont""" zont = Zont(hass, mail, token) @@ -37,24 +63,87 @@ def validate_mail(mail: str) -> None: class ZontConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - VERSION = 1 + VERSION = 2 + data: dict = None async def async_step_user(self, user_input=None): errors: dict[str, str] = {} if user_input is not None: try: - _LOGGER.debug(user_input) + _LOGGER.debug(f'user input start: {user_input}') validate_mail(user_input['mail']) - await validate_auth( + if not errors: + self.data = user_input + if user_input.get('get_token', False): + return await self.async_step_auth_pswd() + else: + return await self.async_step_auth_token() + except InvalidMail: + _LOGGER.error(f"{user_input['mail']} - неверный формат") + errors['base'] = 'invalid_mail' + except Exception as e: + _LOGGER.error(f'Что-то пошло не так, неизвестная ошибка. {e}') + errors["base"] = "unknown" + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(schema="name"): str, + vol.Required(schema="mail"): str, + vol.Optional(schema="get_token"): bool + } + ), + errors=errors + ) + + async def async_step_auth_pswd(self, user_input=None): + errors: dict[str, str] = {} + if user_input is not None: + try: + _LOGGER.debug(f'data: {self.data}') + _LOGGER.debug(f'user input pswd: {user_input}') + token = await get_token( + self.hass, + user_input['mail'], + user_input['login'], + user_input['password'] + ) + self.data['token'] = token + if not errors: + self.data['token'] = 'asdfghjkl' + return await self.async_step_auth_token() + except RequestAPIZONTError: + _LOGGER.error(self.hass.data['error']) + errors['base'] = 'invalid_auth' + except Exception as e: + _LOGGER.error(f'Что-то пошло не так, неизвестная ошибка. {e}') + errors["base"] = "unknown" + return self.async_show_form( + step_id="auth_pswd", + data_schema=vol.Schema( + { + vol.Required("login"): str, + vol.Required("password"): str + } + ), + errors=errors + ) + + async def async_step_auth_token(self, user_input=None): + errors: dict[str, str] = {} + if user_input is not None: + try: + _LOGGER.debug(f'data: {self.data}') + _LOGGER.debug(f'user input pswd: {user_input}') + await validate_auth_token( self.hass, user_input['mail'], user_input['token'] ) + if not errors: + self.data.update(user_input) return self.async_create_entry( - title=user_input['name'], data=user_input + title=self.data['name'], data=self.data ) - except InvalidMail: - _LOGGER.error(f"{user_input['mail']} - неверный формат") - errors['base'] = 'invalid_mail' except RequestAPIZONTError: _LOGGER.error(self.hass.data['error']) errors['base'] = 'invalid_auth' @@ -62,12 +151,12 @@ async def async_step_user(self, user_input=None): _LOGGER.error(f'Что-то пошло не так, неизвестная ошибка. {e}') errors["base"] = "unknown" return self.async_show_form( - step_id="user", + step_id="auth_token", data_schema=vol.Schema( { - vol.Required("name"): str, - vol.Required("mail"): str, - vol.Required("token"): str + vol.Required( + schema="token", default=self.data.get('token', None) + ): str } ), errors=errors diff --git a/custom_components/zont_ha/const.py b/custom_components/zont_ha/const.py index 1ddfd53..0e798e7 100644 --- a/custom_components/zont_ha/const.py +++ b/custom_components/zont_ha/const.py @@ -5,9 +5,9 @@ DOMAIN = 'zont_ha' MANUFACTURER = 'MicroLine' -ZONT_API_URL = "https://lk.zont-online.ru/api/widget/v2/" +ZONT_API_URL = 'https://lk.zont-online.ru/api/widget/v2/' -URL_SEND_COMMAND_ZONT_OLD = "https://lk.zont-online.ru/api/send_z3k_command" +URL_SEND_COMMAND_ZONT_OLD = 'https://lk.zont-online.ru/api/send_z3k_command' URL_GET_DEVICES = 'https://lk.zont-online.ru/api/widget/v2/get_devices' URL_SET_TARGET_TEMP = 'https://lk.zont-online.ru/api/widget/v2/set_target_temp' diff --git a/custom_components/zont_ha/core/models_zont.py b/custom_components/zont_ha/core/models_zont.py index b845a26..1bbbb11 100644 --- a/custom_components/zont_ha/core/models_zont.py +++ b/custom_components/zont_ha/core/models_zont.py @@ -101,6 +101,14 @@ class AccountZont(BaseModel): ok: bool +class TokenZont(BaseModel): + """Клас ответа об ошибке""" + + token: str + token_id: str + ok: bool + + class ErrorZont(BaseModel): """Клас ответа об ошибке""" diff --git a/custom_components/zont_ha/core/zont.py b/custom_components/zont_ha/core/zont.py index a626fe9..0ef19ad 100644 --- a/custom_components/zont_ha/core/zont.py +++ b/custom_components/zont_ha/core/zont.py @@ -54,7 +54,7 @@ def __init__(self, hass: HomeAssistantType, mail: str, token: str): } self.mail = mail self.session = async_get_clientsession(hass) - _LOGGER.warning(f'Создан объект Zont') + _LOGGER.debug(f'Создан объект Zont') async def get_update(self): """Получаем обновление данных объекта Zont""" diff --git a/custom_components/zont_ha/translations/en.json b/custom_components/zont_ha/translations/en.json index 3bf95b9..333769e 100644 --- a/custom_components/zont_ha/translations/en.json +++ b/custom_components/zont_ha/translations/en.json @@ -12,7 +12,20 @@ "data": { "name": "Название", "mail": "mail", - "token": "токен аккаунта" + "get_token": "Получить токен" + } + }, + "auth_pswd": { + "title": "Получить токен", + "data": { + "login": "Логин", + "password": "Пароль" + } + }, + "auth_token": { + "title": "Авторизация в ZONT", + "data": { + "token": "Токен" } } } From f855846152f7d0f4b5cdf6c02feb4f06155ff953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D1=85=D0=B0=D0=B8=D0=BB=20=D0=A8=D1=83=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Thu, 25 Jan 2024 22:12:31 +0300 Subject: [PATCH 2/2] CONFIG FLOW: add get token --- README.md | 7 +++++- custom_components/zont_ha/__init__.py | 25 +++++++++---------- custom_components/zont_ha/config_flow.py | 25 +++++++++---------- custom_components/zont_ha/core/models_zont.py | 2 +- custom_components/zont_ha/manifest.json | 2 +- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 8a96f68..02d5655 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,17 @@ ![pydantic version](https://img.shields.io/badge/pydantic-ha-yellowgreen?style=plastic&logo=fastapi) ![aiohttp version](https://img.shields.io/badge/aiohttp-ha-yellowgreen?style=plastic) ![Home Assistant](https://img.shields.io/badge/HomeAssistant-latest-yellowgreen?style=plastic&logo=homeassistant) + + [![Donate](https://img.shields.io/badge/donate-Tinkoff-FFDD2D.svg)](https://www.tinkoff.ru/rm/shutov.mikhail19/wUyu873109) ## Описание Компонент для управления устройствами [ZONT](https://zont-online.ru/) из Home Assistant. -Для входа в ваш аккаунт потребуется токен. Токен можно получить [здесь](https://lk.zont-online.ru/widget-api/v2). +Для входа в ваш аккаунт потребуется токен. Его можно получить из Home Assistant. +При добавлении устройства нажмите галочку "Получить токен". При каждом получении токена +создаётся новый. Что бы их не плодить на аккаунте ZONT, запишите полученный токен. +Как удалить токен описано [здесь](https://lk.zont-online.ru/widget-api/v2). После авторизации в Home Assistant (далее НА) добавляются все устройства аккаунта. diff --git a/custom_components/zont_ha/__init__.py b/custom_components/zont_ha/__init__.py index 0e0e224..6fdbcb4 100644 --- a/custom_components/zont_ha/__init__.py +++ b/custom_components/zont_ha/__init__.py @@ -16,19 +16,18 @@ async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry) -> bool: - _LOGGER.debug(f'config entry: {config_entry.data}') - # entry_id = config_entry.entry_id - # email = config_entry.data.get("mail") - # token = config_entry.data.get("token") - # zont = Zont(hass, email, token) - # coordinator = ZontCoordinator(hass, zont) - # await coordinator.async_config_entry_first_refresh() - # - # hass.data.setdefault(DOMAIN, {}) - # hass.data[DOMAIN][entry_id] = coordinator - # await hass.config_entries.async_forward_entry_setups( - # config_entry, PLATFORMS - # ) + entry_id = config_entry.entry_id + email = config_entry.data.get("mail") + token = config_entry.data.get("token") + zont = Zont(hass, email, token) + coordinator = ZontCoordinator(hass, zont) + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry_id] = coordinator + await hass.config_entries.async_forward_entry_setups( + config_entry, PLATFORMS + ) return True diff --git a/custom_components/zont_ha/config_flow.py b/custom_components/zont_ha/config_flow.py index d40ecc3..b3d1c78 100644 --- a/custom_components/zont_ha/config_flow.py +++ b/custom_components/zont_ha/config_flow.py @@ -20,18 +20,23 @@ async def get_token( hass: HomeAssistant, mail: str, login: str, password: str ) -> str: session = async_get_clientsession(hass) - basic = f'{login}:{password}'.encode("utf-8") - basic = base64.b64decode(basic).decode() + encoded = f'{login}:{password}'.encode("utf-8") + basic = base64.b64encode(encoded).decode() headers = { 'Authorization': f'Basic {basic}', 'X-ZONT-Client': mail, 'Content-Type': 'application/json' } - response = await session.post(url=URL_GET_TOKEN, headers=headers) + response = await session.post( + url=URL_GET_TOKEN, + json={'client_name': 'Home Assistant'}, + headers=headers + ) text = await response.text() status_code = response.status if status_code != HTTPStatus.OK: error = ErrorZont.parse_raw(text) + hass.data['error'] = error.error_ui raise RequestAPIZONTError(error) data = TokenZont.parse_raw(text) return data.token @@ -45,7 +50,6 @@ async def validate_auth_token( zont = Zont(hass, mail, token) result = await zont.get_update() - _LOGGER.debug(f'validate_auth: {result}') if result != HTTPStatus.OK: hass.data['error'] = zont.error raise RequestAPIZONTError @@ -70,7 +74,6 @@ async def async_step_user(self, user_input=None): errors: dict[str, str] = {} if user_input is not None: try: - _LOGGER.debug(f'user input start: {user_input}') validate_mail(user_input['mail']) if not errors: self.data = user_input @@ -100,17 +103,14 @@ async def async_step_auth_pswd(self, user_input=None): errors: dict[str, str] = {} if user_input is not None: try: - _LOGGER.debug(f'data: {self.data}') - _LOGGER.debug(f'user input pswd: {user_input}') token = await get_token( self.hass, - user_input['mail'], + self.data['mail'], user_input['login'], user_input['password'] ) - self.data['token'] = token if not errors: - self.data['token'] = 'asdfghjkl' + self.data['token'] = token return await self.async_step_auth_token() except RequestAPIZONTError: _LOGGER.error(self.hass.data['error']) @@ -133,11 +133,10 @@ async def async_step_auth_token(self, user_input=None): errors: dict[str, str] = {} if user_input is not None: try: - _LOGGER.debug(f'data: {self.data}') - _LOGGER.debug(f'user input pswd: {user_input}') await validate_auth_token( self.hass, - user_input['mail'], user_input['token'] + self.data['mail'], + user_input['token'] ) if not errors: self.data.update(user_input) diff --git a/custom_components/zont_ha/core/models_zont.py b/custom_components/zont_ha/core/models_zont.py index 1bbbb11..9b6034a 100644 --- a/custom_components/zont_ha/core/models_zont.py +++ b/custom_components/zont_ha/core/models_zont.py @@ -102,7 +102,7 @@ class AccountZont(BaseModel): class TokenZont(BaseModel): - """Клас ответа об ошибке""" + """Клас ответа получения токена""" token: str token_id: str diff --git a/custom_components/zont_ha/manifest.json b/custom_components/zont_ha/manifest.json index 77a025e..ed40744 100644 --- a/custom_components/zont_ha/manifest.json +++ b/custom_components/zont_ha/manifest.json @@ -7,5 +7,5 @@ "integration_type": "hub", "dependencies": ["http", "zeroconf"], "requirements": [], - "version": "0.1.5" + "version": "0.2.0" } \ No newline at end of file