Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 88 additions & 154 deletions custom_components/momentary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,189 +5,123 @@

from __future__ import annotations

import contextlib
import logging

import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_SOURCE, Platform
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import SERVICE_RELOAD, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device import async_remove_stale_devices_links_keep_current_device
from homeassistant.helpers.reload import async_integration_yaml_config
from homeassistant.helpers.typing import ConfigType

from .cfg import BlendedCfg
from .const import (
ATTR_DEVICES,
ATTR_FILE_NAME,
ATTR_GROUP_NAME,
ATTR_SWITCHES,
COMPONENT_CONFIG,
COMPONENT_DOMAIN,
COMPONENT_MANUFACTURER,
COMPONENT_MODEL,
CONF_YAML_CONFIG,
)

__version__ = "0.7.0b13"
from .const import CONF_DEVICE_ID, CONF_NAME, CONF_YAML_PRESENT, CONF_YAML_SWITCH, DOMAIN
from .helpers import async_process_yaml

_LOGGER = logging.getLogger(__name__)

CONFIG_SCHEMA = vol.Schema(
{
COMPONENT_DOMAIN: vol.Schema(
{
vol.Optional(CONF_YAML_CONFIG, default=False): cv.boolean,
}
),
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up an momentary component."""
# hass.data.setdefault(COMPONENT_DOMAIN, {})

if COMPONENT_DOMAIN not in hass.data:
hass.data[COMPONENT_DOMAIN] = {}
hass.data[COMPONENT_CONFIG] = {}

# See if yaml support was enabled.
if not config.get(COMPONENT_DOMAIN, {}).get(CONF_YAML_CONFIG, False):
# New style. We import old config if needed.
_LOGGER.debug("setting up new momentary components")
hass.data[COMPONENT_CONFIG][CONF_YAML_CONFIG] = False

# See if we have already imported the data. If we haven't then do it now.
config_entry = _async_find_matching_config_entry(hass)
if not config_entry:
_LOGGER.debug("importing a YAML setup")
hass.async_create_task(
hass.config_entries.flow.async_init(
COMPONENT_DOMAIN,
context={CONF_SOURCE: SOURCE_IMPORT},
data=config.get(Platform.SWITCH, []),
)
)
"""Set up the momentary integration and register services.

async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{COMPONENT_DOMAIN}",
is_fixable=False,
issue_domain=COMPONENT_DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": COMPONENT_DOMAIN,
"integration_title": "Momentary",
},
)
This initializes integration data structures on hass.data and registers
a reload service that can reprocess YAML configuration when requested.

else:
_LOGGER.debug("ignoring a YAML setup")
Parameters
----------
hass : HomeAssistant
The Home Assistant instance.
config : ConfigType
The configuration passed from Home Assistant.

else:
_LOGGER.debug("setting up old momentary components")
hass.data[COMPONENT_CONFIG][CONF_YAML_CONFIG] = True

return True
Returns
-------
bool
True if setup succeeded and YAML processing completed, False otherwise.

"""
hass.data.setdefault(DOMAIN, {})

@callback
def _async_find_matching_config_entry(hass: HomeAssistant) -> ConfigEntry | None:
"""Return a matching config entry for this integration.
async def _async_reload_service_handler(service: ServiceCall) -> None:
_LOGGER.info("Service %s.reload called. Reloading YAML integration", DOMAIN)
reload_config = None
with contextlib.suppress(HomeAssistantError):
reload_config = await async_integration_yaml_config(hass, DOMAIN)
if reload_config is None:
return
# _LOGGER.debug("reload_config: %s", reload_config)
await async_process_yaml(hass, reload_config)

If a config entry exists for the integration we consider the integration
configured and will ignore any YAML configuration.
"""
for entry in hass.config_entries.async_entries(COMPONENT_DOMAIN):
return entry
return None
hass.services.async_register(DOMAIN, SERVICE_RELOAD, _async_reload_service_handler)
# _LOGGER.debug("[async_setup] config: %s", config)
return await async_process_yaml(hass, config)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry for the momentary integration.

This loads stored devices and creates the switch entities for the entry.
Parameters
----------
hass : HomeAssistant
The Home Assistant instance.
entry : ConfigEntry
The configuration entry being set up.

Returns
-------
bool
True if the entry was successfully set up, False otherwise.

"""
_LOGGER.debug("async setup %s", entry.data)

if COMPONENT_DOMAIN not in hass.data:
hass.data[COMPONENT_DOMAIN] = {}
hass.data[COMPONENT_CONFIG] = {}

# Database of devices
group_name = entry.data[ATTR_GROUP_NAME]
file_name = entry.data[ATTR_FILE_NAME]
cfg = BlendedCfg(hass, group_name, file_name)
await cfg.async_load()

# Load and create devices.
for device, name in cfg.devices.items():
_LOGGER.debug("would try to add device %s/%s", device, name)
await _async_get_or_create_momentary_device_in_registry(hass, entry, device, name)

# Delete orphaned devices.
for switch, values in cfg.orphaned_devices.items():
_LOGGER.debug("would try to delete %s/%s", switch, values)
await _async_delete_momentary_device_from_registry(hass, entry, switch, values)

# Update hass data and queue entry creation.
hass.data[COMPONENT_DOMAIN].update(
{
group_name: {
ATTR_DEVICES: cfg.devices,
ATTR_SWITCHES: cfg.switches,
ATTR_FILE_NAME: file_name,
}
}
# _LOGGER.debug("[async_setup_entry] entry: %s", entry.data)

async_remove_stale_devices_links_keep_current_device(
hass,
entry.entry_id,
entry.data.get(CONF_DEVICE_ID),
)
_LOGGER.debug("update hass data %s", hass.data[COMPONENT_DOMAIN])
await hass.config_entries.async_forward_entry_setups(entry, [Platform.SWITCH])

if entry.data.get(CONF_YAML_SWITCH, False):
if not entry.data.get(CONF_YAML_PRESENT, False):
_LOGGER.warning(
"[YAML] YAML Entry no longer exists. Deleting entry in HA: %s",
entry.data.get(CONF_NAME),
)
hass.async_create_task(hass.config_entries.async_remove(entry.entry_id))
return False
hass_data = {key: value for key, value in entry.data.items() if key != CONF_YAML_PRESENT}
else:
hass_data = dict(entry.data)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = hass_data
await hass.config_entries.async_forward_entry_setups(entry, [Platform.SWITCH])
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
_LOGGER.debug("unloading it %s", entry.data[ATTR_GROUP_NAME])
unload_ok = await hass.config_entries.async_unload_platforms(entry, [Platform.SWITCH])
if unload_ok:
await BlendedCfg.delete_group(hass, entry.data[ATTR_GROUP_NAME])
cfg = hass.data[COMPONENT_DOMAIN].pop(entry.data[ATTR_GROUP_NAME])
for device, name in cfg[ATTR_DEVICES].items():
await _async_delete_momentary_device_from_registry(hass, entry, device, name)
_LOGGER.debug("after hass=%s", hass.data[COMPONENT_DOMAIN])
"""Unload a config entry for the momentary integration.

return unload_ok
This cleans up the entry by unloading its platforms and removing stored
data for the entry from hass.data.

Parameters
----------
hass : HomeAssistant
The Home Assistant instance.
entry : ConfigEntry
The configuration entry being unloaded.

async def _async_get_or_create_momentary_device_in_registry(
hass: HomeAssistant, entry: ConfigEntry, device_id: str, name: str
) -> None:
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(COMPONENT_DOMAIN, device_id)},
manufacturer=COMPONENT_MANUFACTURER,
name=name,
model=COMPONENT_MODEL,
sw_version=__version__,
)
Returns
-------
bool
True if the platforms were unloaded successfully, False otherwise.

"""

async def _async_delete_momentary_device_from_registry(
hass: HomeAssistant, _entry: ConfigEntry, device_id: str, _name: str
) -> None:
device_registry = dr.async_get(hass)
device = device_registry.async_get_device(
identifiers={(COMPONENT_DOMAIN, device_id)},
)
if device:
_LOGGER.debug("found something to delete! %s", device.id)
device_registry.async_remove_device(device.id)
else:
_LOGGER.info("have orphaned device in meta %s", device_id)
_LOGGER.info("Unloading: %s", entry.data)
unload_ok = await hass.config_entries.async_unload_platforms(entry, [Platform.SWITCH])
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id, None)

return unload_ok
Loading
Loading