Skip to content

Commit

Permalink
- Fixed Unload/reload integration
Browse files Browse the repository at this point in the history
- Optimized ConfigFlow/OptionsFlow
- New Requirement: HA 2025.1.0
  • Loading branch information
alexdelprete committed Jan 13, 2025
1 parent ca52eb5 commit 3a41442
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 60 deletions.
File renamed without changes.
79 changes: 38 additions & 41 deletions custom_components/4noks_elios4you/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,6 @@ class RuntimeData:
update_listener: Callable


def get_instance_count(hass: HomeAssistant) -> int:
"""Return number of instances."""
entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if not entry.disabled_by
]
return len(entries)


async def async_setup_entry(hass: HomeAssistant, config_entry: Elios4YouConfigEntry):
"""Set up integration from a config entry."""

Expand All @@ -73,7 +63,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: Elios4YouConfigEn

# Initialise a listener for config flow options changes.
# See config_flow for defining an options setting that shows up as configure on the integration.
update_listener = config_entry.add_update_listener(_async_update_listener)
update_listener = config_entry.add_update_listener(async_reload_entry)

# Register an update listener to the config entry that will be called when the entry is updated
# ref.: https://developers.home-assistant.io/docs/config_entries_options_flow_handler/#signal-updates
config_entry.async_on_unload(update_listener)

# Add the coordinator and update listener to hass data to make
# accessible throughout your integration
Expand Down Expand Up @@ -111,12 +105,6 @@ async def async_update_device_registry(
)


async def _async_update_listener(hass: HomeAssistant, config_entry):
"""Handle config options update."""
# Reload the integration when the options change.
await hass.config_entries.async_reload(config_entry.entry_id)


async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry, device_entry
) -> bool:
Expand All @@ -139,34 +127,43 @@ async def async_unload_entry(
# This is called when you remove your integration or shutdown HA.
# If you have created any custom services, they need to be removed here too.

_LOGGER.debug("Unload config_entry")

# This is called when you remove your integration or shutdown HA.
# If you have created any custom services, they need to be removed here too.

# Remove the config options update listener
_LOGGER.debug("Detach config update listener")
hass.data[DOMAIN][config_entry.entry_id].update_listener()
_LOGGER.debug("Unload config_entry: started")

# Check if there are other instances
if get_instance_count(hass) == 0:
_LOGGER.debug("Unload config_entry: no more entries found")
# Unload platforms and cleanup resources
if unload_ok := await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
):
try:
# Close API connection if exists
# coordinator = getattr(config_entry.runtime_data, 'coordinator', None)
coordinator = config_entry.runtime_data.coordinator
if coordinator.api:
coordinator.api.close()
_LOGGER.debug("Closed API connection")

# Remove update listener if exists
if config_entry.entry_id in hass.data[DOMAIN]:
update_listener = config_entry.runtime_data.update_listener
if update_listener:
update_listener()
_LOGGER.debug("Removed update listener")

# Remove config entry from hass data
hass.data[DOMAIN].pop(config_entry.entry_id)
_LOGGER.debug("Removed config entry from hass data")
except Exception as ex:
_LOGGER.error("Error during unload: %s", str(ex))
return False
else:
_LOGGER.debug("Failed to unload platforms")

_LOGGER.debug("Unload integration platforms")
_LOGGER.debug("Unload config_entry: completed")
return unload_ok

# Unload platforms
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)

# Remove the config entry from the hass data object.
if unload_ok:
_LOGGER.debug("Unload integration")
hass.data[DOMAIN].pop(config_entry.entry_id)
return True # unloaded
else:
_LOGGER.debug("Unload config_entry failed: integration not unloaded")
return False # unload failed
async def async_reload_entry(hass: HomeAssistant, config_entry: Elios4YouConfigEntry):
"""Reload the config entry."""
await hass.config_entries.async_schedule_reload(config_entry.entry_id)


# Sample migration code in case it's needed
Expand Down
37 changes: 19 additions & 18 deletions custom_components/4noks_elios4you/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@

import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.selector import selector
from pymodbus.exceptions import ConnectionException

from .api import Elios4YouAPI
from .const import (
Expand All @@ -29,10 +35,6 @@
_LOGGER = logging.getLogger(__name__)


class ConnectionError(Exception):
"""Empty Error Class."""


def host_valid(host):
"""Return True if hostname or IP address is valid."""
try:
Expand All @@ -52,7 +54,7 @@ def get_host_from_config(hass: HomeAssistant):
}


class Elios4YouConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
class Elios4YouConfigFlow(ConfigFlow, domain=DOMAIN):
"""4-noks Elios4You config flow."""

VERSION = 1
Expand All @@ -70,7 +72,7 @@ def _host_in_configuration_exists(self, host) -> bool:
return True
return False

async def test_connection(self, name, host, port):
async def get_unique_id(self, name, host, port):
"""Return true if credentials is valid."""
_LOGGER.debug(f"Test connection to {host}:{port}")
try:
Expand All @@ -81,13 +83,13 @@ async def test_connection(self, name, host, port):
_LOGGER.debug("API Client: get data")
_LOGGER.debug(f"API Client Data: {self.api_data}")
return self.api.data["sn"]
except ConnectionError as connerr:
except ConnectionException as connerr:
_LOGGER.error(
f"Failed to connect to host: {host}:{port} - Exception: {connerr}"
)
return False

async def async_step_user(self, user_input=None):
async def async_step_user(self, user_input=None) -> ConfigFlowResult:
"""Handle the initial step."""
errors = {}

Expand All @@ -101,7 +103,7 @@ async def async_step_user(self, user_input=None):
elif not host_valid(user_input[CONF_HOST]):
errors[CONF_HOST] = "invalid Host IP"
else:
uid = await self.test_connection(name, host, port)
uid = await self.get_unique_id(name, host, port)
if uid is not False:
_LOGGER.debug(f"Device unique id: {uid}")
await self.async_set_unique_id(uid)
Expand All @@ -128,7 +130,7 @@ async def async_step_user(self, user_input=None):
vol.Required(
CONF_PORT,
default=DEFAULT_PORT,
): vol.Coerce(int),
): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
vol.Required(
CONF_SCAN_INTERVAL,
default=DEFAULT_SCAN_INTERVAL,
Expand All @@ -149,27 +151,26 @@ async def async_step_user(self, user_input=None):
)


class Elios4YouOptionsFlow(config_entries.OptionsFlow):
class Elios4YouOptionsFlow(OptionsFlow):
"""Config flow options handler."""

VERSION = 1

def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize option flow instance."""
self.config_entry = config_entry
self.data_schema = vol.Schema(
{
vol.Required(
CONF_HOST,
default=self.config_entry.data.get(CONF_HOST),
default=config_entry.data.get(CONF_HOST),
): cv.string,
vol.Required(
CONF_PORT,
default=self.config_entry.data.get(CONF_PORT),
): vol.Coerce(int),
default=config_entry.data.get(CONF_PORT),
): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
vol.Required(
CONF_SCAN_INTERVAL,
default=self.config_entry.data.get(CONF_SCAN_INTERVAL),
default=config_entry.data.get(CONF_SCAN_INTERVAL),
): selector(
{
"number": {
Expand All @@ -184,7 +185,7 @@ def __init__(self, config_entry: ConfigEntry) -> None:
}
)

async def async_step_init(self, user_input=None):
async def async_step_init(self, user_input=None) -> ConfigFlowResult:
"""Manage the options."""

if user_input is not None:
Expand Down
1 change: 1 addition & 0 deletions custom_components/4noks_elios4you/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"issue_tracker": "https://github.com/alexdelprete/ha-4noks-elios4you/issues",
"loggers": ["custom_components.4noks_elios4you"],
"requirements": ["telnetlib3>=2.0.4"],
"single_config_entry": false,
"version": "0.1.0"
}
1 change: 1 addition & 0 deletions custom_components/4noks_elios4you/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def _handle_coordinator_update(self) -> None:
"_handle_coordinator_update: sensors state written to state machine"
)

# when has_entity_name is True, the resulting entity name will be: {device_name}_{self._name}
@property
def has_entity_name(self):
"""Return the name state."""
Expand Down
6 changes: 6 additions & 0 deletions custom_components/4noks_elios4you/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def _handle_coordinator_update(self) -> None:
self.async_write_ha_state()
_LOGGER.debug(f"{self.name} switch coord. update requested")

# when has_entity_name is True, the resulting entity name will be: {device_name}_{self._name}
@property
def has_entity_name(self):
"""Return the name state."""
return True

@property
def name(self):
"""Return the name of the Device."""
Expand Down
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "4-noks Elios4you",
"homeassistant": "2024.6.0",
"homeassistant": "2025.1.0",
"content_in_root": false,
"hide_default_branch": false,
"render_readme": true,
Expand Down

0 comments on commit 3a41442

Please sign in to comment.