Skip to content

Commit

Permalink
Merge pull request #146 from sh00t2kill/develop
Browse files Browse the repository at this point in the history
Develop v1.0.3
  • Loading branch information
elad-bar authored Jul 30, 2023
2 parents 6dc0643 + 5d9e562 commit 66edd3c
Show file tree
Hide file tree
Showing 19 changed files with 580 additions and 264 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## v1.0.3

- Improve AWS client connection recovery process (unsubscribe topics and disconnect before trying to connect again)
- Refactor password encryption flow
- Refactor integration loading (will be loaded only after HA is loaded, re-load will be performed immediately)
- Add to integration unload call to unsubscribe AWS client topics
- Integration setup
- Add support for edit integration details
- Add title to the form (Add / Edit), will set the integration name

## v1.0.2

- Breaking change: Renamed sensor of `Main Unit Status` to `Power Supply Status`
Expand Down
33 changes: 21 additions & 12 deletions custom_components/mydolphin_plus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
import sys

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.core import HomeAssistant

from .common.consts import DEFAULT_NAME, DOMAIN, PLATFORMS
from .common.exceptions import LoginError
from .managers.config_manager import ConfigManager
from .managers.coordinator import MyDolphinPlusCoordinator
from .managers.password_manager import PasswordManager
from .models.exceptions import LoginError

_LOGGER = logging.getLogger(__name__)

Expand All @@ -26,24 +28,27 @@ 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 = ConfigManager(hass, entry)
await config_manager.initialize()
await config_manager.initialize(entry_config)

is_initialized = config_manager.is_initialized

if is_initialized:
coordinator = MyDolphinPlusCoordinator(hass, config_manager)
await coordinator.initialize()

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

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

_LOGGER.info(
f"Start loading {DOMAIN} integration, Entry ID: {entry.entry_id}"
)
if hass.is_running:
await coordinator.initialize()

await coordinator.async_config_entry_first_refresh()
else:
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, coordinator.on_home_assistant_start
)

_LOGGER.info("Finished loading integration")

Expand All @@ -67,13 +72,17 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
_LOGGER.info(f"Unloading {DOMAIN} integration, Entry ID: {entry.entry_id}")

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

coordinator: MyDolphinPlusCoordinator = hass.data[DOMAIN][entry_id]

await coordinator.terminate()

await coordinator.config_manager.remove()
await coordinator.config_manager.remove(entry_id)

for platform in PLATFORMS:
await hass.config_entries.async_forward_entry_unload(entry, platform)

del hass.data[DOMAIN][entry.entry_id]
del hass.data[DOMAIN][entry_id]

return True
15 changes: 5 additions & 10 deletions custom_components/mydolphin_plus/common/base_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,17 @@
import sys
from typing import Any

from custom_components.mydolphin_plus import (
DOMAIN,
ConfigManager,
MyDolphinPlusCoordinator,
)
from custom_components.mydolphin_plus.common.consts import DATA_ROBOT_FAMILY
from custom_components.mydolphin_plus.common.entity_descriptions import (
MyDolphinPlusEntityDescription,
get_entity_descriptions,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import slugify

from ..managers.config_manager import ConfigManager
from ..managers.coordinator import MyDolphinPlusCoordinator
from .consts import DATA_ROBOT_FAMILY, DOMAIN
from .entity_descriptions import MyDolphinPlusEntityDescription, get_entity_descriptions

_LOGGER = logging.getLogger(__name__)


Expand Down
16 changes: 16 additions & 0 deletions custom_components/mydolphin_plus/common/connectivity_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,19 @@ def get_log_level(status: StrEnum) -> int:
return logging.WARNING
else:
return logging.ERROR

@staticmethod
def get_ha_error(status: str) -> str | None:
errors = {
str(ConnectivityStatus.InvalidCredentials): "invalid_admin_credentials",
str(ConnectivityStatus.MissingAPIKey): "missing_permanent_api_key",
str(ConnectivityStatus.Failed): "invalid_server_details",
str(ConnectivityStatus.NotFound): "invalid_server_details",
}

error_id = errors.get(status)

return error_id


IGNORED_TRANSITIONS = {ConnectivityStatus.Disconnected: [ConnectivityStatus.Failed]}
1 change: 1 addition & 0 deletions custom_components/mydolphin_plus/common/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
INVALID_TOKEN_SECTION = "https://github.com/sh00t2kill/dolphin-robot#invalid-token"

ENTRY_ID_CONFIG = "config"
CONF_TITLE = "title"

SIGNAL_DEVICE_NEW = f"{DOMAIN}_NEW_DEVICE_SIGNAL"
SIGNAL_AWS_CLIENT_STATUS = f"{DOMAIN}_AWS_CLIENT_STATUS_SIGNAL"
Expand Down
57 changes: 29 additions & 28 deletions custom_components/mydolphin_plus/common/entity_descriptions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
from dataclasses import dataclass

from custom_components.mydolphin_plus.common.clean_modes import (
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntityDescription,
)
from homeassistant.components.light import LightEntityDescription
from homeassistant.components.number import NumberDeviceClass, NumberEntityDescription
from homeassistant.components.select import SelectEntityDescription
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.components.vacuum import (
StateVacuumEntityDescription,
VacuumEntityFeature,
)
from homeassistant.const import (
SIGNAL_STRENGTH_DECIBELS,
EntityCategory,
Platform,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.helpers.entity import EntityDescription
from homeassistant.util import slugify

from .clean_modes import (
CleanModes,
get_clean_mode_cycle_time_key,
get_clean_mode_cycle_time_name,
)
from custom_components.mydolphin_plus.common.consts import (
from .consts import (
DATA_KEY_AWS_BROKER,
DATA_KEY_CLEAN_MODE,
DATA_KEY_CYCLE_COUNT,
Expand All @@ -28,32 +54,7 @@
ICON_LED_MODES,
VACUUM_FEATURES,
)
from custom_components.mydolphin_plus.common.robot_family import RobotFamily
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntityDescription,
)
from homeassistant.components.light import LightEntityDescription
from homeassistant.components.number import NumberDeviceClass, NumberEntityDescription
from homeassistant.components.select import SelectEntityDescription
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.components.vacuum import (
StateVacuumEntityDescription,
VacuumEntityFeature,
)
from homeassistant.const import (
SIGNAL_STRENGTH_DECIBELS,
EntityCategory,
Platform,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.helpers.entity import EntityDescription
from homeassistant.util import slugify
from .robot_family import RobotFamily


@dataclass(slots=True)
Expand Down
54 changes: 23 additions & 31 deletions custom_components/mydolphin_plus/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@

import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback

from .common.connectivity_status import ConnectivityStatus
from .common.consts import DEFAULT_NAME, DOMAIN
from .managers.config_manager import ConfigManager
from .managers.rest_api import RestAPI
from .common.consts import DOMAIN
from .managers.flow_manager import IntegrationFlowManager

_LOGGER = logging.getLogger(__name__)

Expand All @@ -26,37 +23,32 @@ class DomainFlowHandler(config_entries.ConfigFlow):
def __init__(self):
super().__init__()

@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return DomainOptionsFlowHandler(config_entry)

async def async_step_user(self, user_input=None):
"""Handle a flow start."""
_LOGGER.debug(f"Starting async_step_user of {DEFAULT_NAME}")

errors = None

if user_input is not None:
config_manager = ConfigManager(self.hass, None)
config_manager.update_credentials(user_input)
flow_manager = IntegrationFlowManager(self.hass, self)

await config_manager.initialize()
return await flow_manager.async_step(user_input)

api = RestAPI(self.hass, config_manager)

await api.validate()
class DomainOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle domain options."""

if api.status == ConnectivityStatus.TemporaryConnected:
_LOGGER.debug("User inputs are valid")
_config_entry: ConfigEntry

user_input[CONF_PASSWORD] = config_manager.password_hashed

return self.async_create_entry(title=DEFAULT_NAME, data=user_input)

else:
_LOGGER.warning("Failed to create integration")
def __init__(self, config_entry: ConfigEntry):
"""Initialize domain options flow."""
super().__init__()

new_user_input = {
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
self._config_entry = config_entry

schema = vol.Schema(new_user_input)
async def async_step_init(self, user_input=None):
"""Manage the domain options."""
flow_manager = IntegrationFlowManager(self.hass, self, self._config_entry)

return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
return await flow_manager.async_step(user_input)
27 changes: 19 additions & 8 deletions custom_components/mydolphin_plus/managers/aws_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from homeassistant.helpers.dispatcher import async_dispatcher_send

from ..common.clean_modes import CleanModes
from ..common.connectivity_status import ConnectivityStatus
from ..common.connectivity_status import IGNORED_TRANSITIONS, ConnectivityStatus
from ..common.consts import (
API_DATA_MOTOR_UNIT_SERIAL,
API_DATA_SERIAL_NUMBER,
Expand Down Expand Up @@ -78,7 +78,7 @@
WS_DATA_VERSION,
WS_LAST_UPDATE,
)
from ..common.topic_data import TopicData
from ..models.topic_data import TopicData
from .config_manager import ConfigManager

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -133,9 +133,18 @@ def data(self) -> dict:

async def terminate(self):
if self._awsiot_client is not None:
topics = self._topic_data.subscribe
_LOGGER.debug(f"Unsubscribing topics: {', '.join(topics)}")
for topic in self._topic_data.subscribe:
self._awsiot_client.unsubscribeAsync(topic)

_LOGGER.debug("Disconnecting AWS Client")
self._awsiot_client.disconnectAsync(self._ack_callback)

self._awsiot_client = None

self._set_status(ConnectivityStatus.Disconnected)
_LOGGER.debug("AWS Client is disconnected")

async def initialize(self):
try:
Expand Down Expand Up @@ -475,6 +484,11 @@ def _get_led_settings(self, key, value):

def _set_status(self, status: ConnectivityStatus):
if status != self._status:
ignored_transitions = IGNORED_TRANSITIONS.get(self._status, [])

if status in ignored_transitions:
return

log_level = ConnectivityStatus.get_log_level(status)

_LOGGER.log(
Expand All @@ -485,7 +499,6 @@ def _set_status(self, status: ConnectivityStatus):
self._status = status

self._async_dispatcher_send(
self._hass,
SIGNAL_AWS_CLIENT_STATUS,
self._config_manager.entry_id,
status,
Expand All @@ -494,11 +507,9 @@ def _set_status(self, status: ConnectivityStatus):
def set_local_async_dispatcher_send(self, callback):
self._local_async_dispatcher_send = callback

def _async_dispatcher_send(
self, hass: HomeAssistant, signal: str, *args: Any
) -> None:
if hass is None:
def _async_dispatcher_send(self, signal: str, *args: Any) -> None:
if self._hass is None:
self._local_async_dispatcher_send(signal, *args)

else:
async_dispatcher_send(hass, signal, *args)
async_dispatcher_send(self._hass, signal, *args)
Loading

0 comments on commit 66edd3c

Please sign in to comment.