Skip to content

Commit

Permalink
Merge pull request #69 from radical-squared/improve-config-flow
Browse files Browse the repository at this point in the history
Improve config flow
  • Loading branch information
elad-bar authored Aug 5, 2023
2 parents d9ad888 + a256729 commit 95e29ce
Show file tree
Hide file tree
Showing 25 changed files with 940 additions and 261 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## v3.0.25

### Breaking Change!

Password store format changed, up until now, password saved as the hash being send to the API,

To allow the enhanced functionality added, to edit Credentials and API Type, the password is now being saved encrypted and decrypted in 2 flows:

1. Edit integration details
2. On startup (hashed and sent to the API)

As result of that process, you need to re-setup the integration.

### Changes

- Add to set up configuration form new field of title to define the name of the integration
- Add edit integration details - Title, Credentials and API Type
- Refactor component initialization code to simplify process
- Fix `UndefinedType._singleton` name for climate entity

## v3.0.24

- Add missing configuration for API param 'error_code'
Expand Down
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,72 @@ Attach the following details to issue:
- Attach screenshots and logs as zip file or link for download
- Attach diagnostic details from HA integration

### Invalid Token

In case you have referenced to that section, something went wrong with the encryption key,
Encryption key should be located in `.storage/aqua_temp.config.json` file under `data.key` property,
below are the steps to solve that issue.

#### File not exists or File exists, data.key is not

Please report as issue

#### File exists, data.key is available

Example:

```json
{
"version": 1,
"minor_version": 1,
"key": "aqua_temp.config.json",
"data": {
"key": "ox-qQsAiHb67Kz3ypxY19uU2_YwVcSjvdbaBVHZJQFY="
}
}
```

OR

```json
{
"version": 1,
"minor_version": 1,
"key": "aqua_temp.config.json",
"data": {
"key": "ox-qQsAiHb67Kz3ypxY19uU2_YwVcSjvdbaBVHZJQFY="
}
}
```

1. Remove the integration
2. Delete the file
3. Restart HA
4. Try to re-add the integration
5. If still happens - report as issue

#### File exists, key is available under one of the entry configurations

Example:

```json
{
"version": 1,
"minor_version": 1,
"key": "aqua_temp.config.json",
"data": {
"b8fa11c50331d2647b8aa7e37935efeb": {
"key": "ox-qQsAiHb67Kz3ypxY19uU2_YwVcSjvdbaBVHZJQFY="
}
}
}
```

1. Move the `key` to the root of the JSON
2. Restart HA
3. Try to re-add the integration
4. If still happens - follow instructions of section #1 (_i._)

## Discord

Join our community in [Discord](https://discord.gg/6B6fakaK)
51 changes: 34 additions & 17 deletions custom_components/aqua_temp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import logging
import sys

from cryptography.fernet import InvalidToken

from custom_components.aqua_temp.models.exceptions import LoginError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.core import HomeAssistant

from .common.consts import DOMAIN
from .common.exceptions import LoginError
from .managers.aqua_temp_api import AquaTempAPI
from .common.consts import DEFAULT_NAME, DOMAIN, INVALID_TOKEN_SECTION
from .managers.aqua_temp_config_manager import AquaTempConfigManager
from .managers.aqua_temp_coordinator import AquaTempCoordinator
from .managers.password_manager import PasswordManager

_LOGGER = logging.getLogger(__name__)

Expand All @@ -22,36 +25,48 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
initialized = False

try:
entry_config = {key: entry.data[key] for key in entry.data}

await PasswordManager.decrypt(hass, entry_config, entry.entry_id)

config_manager = AquaTempConfigManager(hass, entry)
await config_manager.initialize()
await config_manager.initialize(entry_config)

api = AquaTempAPI(hass, config_manager)
await api.initialize()
is_initialized = config_manager.is_initialized

coordinator = AquaTempCoordinator(hass, api, config_manager)
if is_initialized:
coordinator = AquaTempCoordinator(hass, config_manager)

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator

await hass.config_entries.async_forward_entry_setups(
entry, config_manager.platforms
)
if hass.is_running:
await coordinator.initialize()

_LOGGER.info(f"Start loading {DOMAIN} integration, Entry ID: {entry.entry_id}")
else:
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, coordinator.on_home_assistant_start
)

await coordinator.async_config_entry_first_refresh()
_LOGGER.info("Finished loading integration")

_LOGGER.info("Finished loading integration")
initialized = is_initialized

initialized = True
except InvalidToken:
_LOGGER.error(
"Corrupted password or encryption key, "
f"please follow steps in {INVALID_TOKEN_SECTION}"
)

except LoginError:
_LOGGER.info("Failed to login Aqua Temp API, cannot log integration")
_LOGGER.error(f"Failed to login {DEFAULT_NAME} API, cannot log integration")

except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno

_LOGGER.error(f"Failed to load Aqua Temp, error: {ex}, line: {line_number}")
_LOGGER.error(
f"Failed to load {DEFAULT_NAME}, error: {ex}, line: {line_number}"
)

return initialized

Expand All @@ -62,6 +77,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):

coordinator: AquaTempCoordinator = hass.data[DOMAIN][entry.entry_id]

await coordinator.config_manager.remove(entry.entry_id)

platforms = coordinator.config_manager.platforms

for platform in platforms:
Expand Down
29 changes: 9 additions & 20 deletions custom_components/aqua_temp/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import HomeAssistant

from .common.base_entity import BaseEntity, async_setup_entities
from .common.consts import API_STATUS, SIGNAL_AQUA_TEMP_DEVICE_NEW
from .common.base_entity import BaseEntity, async_setup_base_entry
from .common.consts import API_STATUS
from .common.entity_descriptions import AquaTempBinarySensorEntityDescription
from .managers.aqua_temp_coordinator import AquaTempCoordinator

Expand All @@ -17,22 +16,12 @@
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
):
@callback
def _async_device_new(entry_id: str, device_code: str):
if entry.entry_id != entry_id:
return

async_setup_entities(
hass,
entry,
Platform.BINARY_SENSOR,
device_code,
AquaTempBinarySensorEntity,
async_add_entities,
)

entry.async_on_unload(
async_dispatcher_connect(hass, SIGNAL_AQUA_TEMP_DEVICE_NEW, _async_device_new)
await async_setup_base_entry(
hass,
entry,
Platform.BINARY_SENSOR,
AquaTempBinarySensorEntity,
async_add_entities,
)


Expand Down
28 changes: 8 additions & 20 deletions custom_components/aqua_temp/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import HomeAssistant

from .common.base_entity import BaseEntity, async_setup_entities
from .common.consts import SIGNAL_AQUA_TEMP_DEVICE_NEW
from .common.base_entity import BaseEntity, async_setup_base_entry
from .common.entity_descriptions import AquaTempClimateEntityDescription
from .managers.aqua_temp_coordinator import AquaTempCoordinator

Expand All @@ -25,22 +23,12 @@
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
):
@callback
def _async_device_new(entry_id: str, device_code: str):
if entry.entry_id != entry_id:
return

async_setup_entities(
hass,
entry,
Platform.CLIMATE,
device_code,
AquaTempClimateEntity,
async_add_entities,
)

entry.async_on_unload(
async_dispatcher_connect(hass, SIGNAL_AQUA_TEMP_DEVICE_NEW, _async_device_new)
await async_setup_base_entry(
hass,
entry,
Platform.CLIMATE,
AquaTempClimateEntity,
async_add_entities,
)


Expand Down
62 changes: 37 additions & 25 deletions custom_components/aqua_temp/common/base_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,58 @@

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import slugify

from ..managers.aqua_temp_coordinator import AquaTempCoordinator
from .consts import DOMAIN
from .consts import ADD_COMPONENT_SIGNALS, DOMAIN
from .entity_descriptions import AquaTempEntityDescription

_LOGGER = logging.getLogger(__name__)


def async_setup_entities(
async def async_setup_base_entry(
hass: HomeAssistant,
entry: ConfigEntry,
platform: Platform,
device_code: str,
entity_type: type,
async_add_entities,
):
try:
coordinator = hass.data[DOMAIN][entry.entry_id]
config_manager = coordinator.config_manager
@callback
def _async_handle_device(entry_id: str, device_code: str):
if entry.entry_id != entry_id:
return

entities = []
try:
coordinator = hass.data[DOMAIN][entry.entry_id]

entity_descriptions = config_manager.get_entity_descriptions(device_code)
config_manager = coordinator.config_manager

for entity_description in entity_descriptions:
if entity_description.platform == platform:
entity = entity_type(entity_description, coordinator, device_code)
entity_descriptions = config_manager.get_entity_descriptions(device_code)

entities.append(entity)
entities = [
entity_type(entity_description, coordinator, device_code)
for entity_description in entity_descriptions
if entity_description.platform == platform
]

_LOGGER.debug(f"Setting up {platform} entities: {entities}")
_LOGGER.debug(f"Setting up {platform} entities: {entities}")

async_add_entities(entities, True)
async_add_entities(entities, True)

except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno

_LOGGER.error(
f"Failed to initialize {platform}, Error: {ex}, Line: {line_number}"
_LOGGER.error(
f"Failed to initialize {platform}, Error: {ex}, Line: {line_number}"
)

for add_component_signal in ADD_COMPONENT_SIGNALS:
entry.async_on_unload(
async_dispatcher_connect(hass, add_component_signal, _async_handle_device)
)


Expand All @@ -66,16 +75,19 @@ def __init__(
self.entity_description = entity_description

device_info = coordinator.get_device(device_code)
identifiers = device_info.get("identifiers")
serial_number = list(identifiers)[0][1]

entity_name = coordinator.config_manager.get_entity_name(
entity_description, device_info
)

unique_id = slugify(
f"{entity_description.platform}_{serial_number}_{entity_description.key}"
)
unique_id_parts = [
DOMAIN,
entity_description.platform,
entity_description.key,
device_code,
]

unique_id = slugify("_".join(unique_id_parts))

self._attr_device_info = device_info
self._attr_name = entity_name
Expand Down
Loading

0 comments on commit 95e29ce

Please sign in to comment.