Skip to content

Commit

Permalink
Add sun2 integration via UI
Browse files Browse the repository at this point in the history
Do not add "home" location when sun2 YAML config is empty.
Remove imported configs no longer in YAML.
Reload configs whenever updated (e.g., when config title changed.)
  • Loading branch information
pnbruckner committed Nov 21, 2023
1 parent 7143890 commit 80ccc05
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 141 deletions.
58 changes: 50 additions & 8 deletions custom_components/sun2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
"""Sun2 integration."""
from __future__ import annotations

import asyncio
from typing import cast

from homeassistant.config_entries import ConfigEntry, SOURCE_IMPORT
from homeassistant.const import EVENT_CORE_CONFIG_UPDATE, Platform, SERVICE_RELOAD
from homeassistant.const import (
CONF_LATITUDE,
CONF_UNIQUE_ID,
EVENT_CORE_CONFIG_UPDATE,
Platform,
SERVICE_RELOAD,
)
from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.reload import async_integration_yaml_config
Expand Down Expand Up @@ -33,44 +40,79 @@ def update_local_loc_data() -> LocData:
)
return loc_data

update_local_loc_data()

def process_config(config: ConfigType) -> None:
async def process_config(config: ConfigType, run_immediately: bool = True) -> None:
"""Process sun2 config."""
for conf in config.get(DOMAIN, []):
hass.async_create_task(
configs = config.get(DOMAIN, [])
unique_ids = [config[CONF_UNIQUE_ID] for config in configs]
tasks = []

for entry in hass.config_entries.async_entries(DOMAIN):
if entry.source != SOURCE_IMPORT:
continue
if entry.unique_id not in unique_ids:
tasks.append(hass.config_entries.async_remove(entry.entry_id))

for conf in configs:
tasks.append(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf.copy()
)
)

process_config(config)
if not tasks:
return

if run_immediately:
await asyncio.gather(*tasks)
else:
for task in tasks:
hass.async_create_task(task)

async def reload_config(call: ServiceCall | None = None) -> None:
"""Reload configuration."""
process_config(await async_integration_yaml_config(hass, DOMAIN))
await process_config(await async_integration_yaml_config(hass, DOMAIN))

async def handle_core_config_update(event: Event) -> None:
"""Handle core config update."""
if not event.data:
return

loc_data = update_local_loc_data()

if not any(key in event.data for key in ["location_name", "language"]):
# Signal all instances that location data has changed.
dispatcher_send(hass, SIG_HA_LOC_UPDATED, loc_data)
return

await reload_config()
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.source == SOURCE_IMPORT:
continue
if CONF_LATITUDE not in entry.options:
reload = not hass.config_entries.async_update_entry(
entry, title=hass.config.location_name
)
else:
reload = True
if reload:
await hass.config_entries.async_reload(entry.entry_id)

update_local_loc_data()
await process_config(config, run_immediately=False)
async_register_admin_service(hass, DOMAIN, SERVICE_RELOAD, reload_config)
hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, handle_core_config_update)

return True


async def entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle config entry update."""
await hass.config_entries.async_reload(entry.entry_id)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up config entry."""
entry.async_on_unload(entry.add_update_listener(entry_updated))
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

Expand Down
59 changes: 23 additions & 36 deletions custom_components/sun2/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
LOC_PARAMS,
LocParams,
Num,
sun2_dev_info,
Sun2Entity,
Sun2EntityParams,
get_loc_params,
nearest_second,
)
Expand Down Expand Up @@ -139,23 +141,21 @@ class Sun2ElevationSensor(Sun2Entity, BinarySensorEntity):
def __init__(
self,
loc_params: LocParams | None,
extra: ConfigEntry | str | None,
extra: Sun2EntityParams | str | None,
name: str,
above: float,
unique_id: str | None = None,
) -> None:
"""Initialize sensor."""
if not isinstance(extra, ConfigEntry):
if not isinstance(extra, Sun2EntityParams):
# Note that entity_platform will add namespace prefix to object ID.
self.entity_id = f"{BINARY_SENSOR_DOMAIN}.{slugify(name)}"
if extra:
name = f"{extra} {name}"
extra = None
self.entity_description = BinarySensorEntityDescription(
key=CONF_ELEVATION, name=name
)
super().__init__(
loc_params, extra if isinstance(extra, ConfigEntry) else None, unique_id
)
super().__init__(loc_params, extra)
self._event = "solar_elevation"

self._threshold: float = above
Expand Down Expand Up @@ -334,40 +334,23 @@ def schedule_update(now: datetime) -> None:
self._attr_extra_state_attributes = {ATTR_NEXT_CHANGE: nxt_dttm}


def _sensors_old(
def _sensors(
loc_params: LocParams | None,
extra: ConfigEntry | str | None,
extra: Sun2EntityParams | str | None,
sensors_config: Iterable[str | dict[str, Any]],
) -> list[Entity]:
"""Create list of entities to add."""
sensors = []
for config in sensors_config:
if CONF_ELEVATION in config:
options = config[CONF_ELEVATION]
sensors.append(
Sun2ElevationSensor(
loc_params, extra, options[CONF_NAME], options[CONF_ABOVE]
)
)
return sensors


def _sensors_new(
loc_params: LocParams | None,
extra: ConfigEntry | str | None,
sensors_config: Iterable[str | dict[str, Any]],
) -> list[Entity]:
sensors = []
for config in sensors_config:
if CONF_ELEVATION in config:
sensors.append(
Sun2ElevationSensor(
loc_params,
extra,
config[CONF_NAME],
config[CONF_ELEVATION],
config[CONF_UNIQUE_ID],
)
)
if isinstance(extra, Sun2EntityParams):
extra.unique_id = config[CONF_UNIQUE_ID]
name = config[CONF_NAME]
above = config[CONF_ELEVATION]
else:
name = config[CONF_ELEVATION][CONF_NAME]
above = config[CONF_ELEVATION][CONF_ABOVE]
sensors.append(Sun2ElevationSensor(loc_params, extra, name, above))
return sensors


Expand All @@ -387,7 +370,7 @@ async def async_setup_platform(
)

async_add_entities(
_sensors_old(
_sensors(
get_loc_params(config),
config.get(CONF_ENTITY_NAMESPACE),
config[CONF_MONITORED_CONDITIONS],
Expand All @@ -407,6 +390,10 @@ async def async_setup_entry(
return

async_add_entities(
_sensors_new(get_loc_params(config), entry, sensors_config),
_sensors(
get_loc_params(config),
Sun2EntityParams(entry, sun2_dev_info(hass, entry)),
sensors_config,
),
True,
)
23 changes: 15 additions & 8 deletions custom_components/sun2/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
CONF_ELEVATION_AT_TIME,
CONF_TIME_AT_ELEVATION,
DOMAIN,
LOGGER,
SUNSET_ELEV,
)
from .helpers import LOC_PARAMS, Sun2Data, translation
Expand All @@ -36,7 +37,9 @@
{
vol.Required(CONF_UNIQUE_ID): cv.string,
vol.Required(CONF_ELEVATION): vol.Any(
vol.All(vol.Lower, "horizon"), vol.Coerce(float)
vol.All(vol.Lower, "horizon"),
vol.Coerce(float),
msg="must be a float or the word horizon",
),
vol.Optional(CONF_NAME): cv.string,
}
Expand All @@ -50,17 +53,24 @@
{vol.Required(CONF_UNIQUE_ID): cv.string}
)


def _sensor(config: ConfigType) -> ConfigType:
"""Validate sensor config."""
if CONF_ELEVATION_AT_TIME in config:
return ELEVATION_AT_TIME_SCHEMA(config)
if CONF_TIME_AT_ELEVATION in config:
return TIME_AT_ELEVATION_SCHEMA(config)
raise vol.Invalid("expected elevation_at_time or time_at_elevation")


_SUN2_LOCATION_CONFIG = vol.Schema(
{
vol.Required(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_LOCATION): cv.string,
vol.Optional(CONF_BINARY_SENSORS): vol.All(
cv.ensure_list, [_SUN2_BINARY_SENSOR_SCHEMA]
),
vol.Optional(CONF_SENSORS): vol.All(
cv.ensure_list,
[vol.Any(ELEVATION_AT_TIME_SCHEMA, TIME_AT_ELEVATION_SCHEMA)],
),
vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [_sensor]),
**LOC_PARAMS,
}
)
Expand Down Expand Up @@ -145,9 +155,6 @@ async def async_validate_config(
config = _SUN2_CONFIG_SCHEMA(config)
if DOMAIN not in config:
return config
if not config[DOMAIN]:
config[DOMAIN] = [{CONF_UNIQUE_ID: "home"}]
return config

for loc_config in config[DOMAIN]:
if CONF_BINARY_SENSORS in loc_config:
Expand Down
Loading

0 comments on commit 80ccc05

Please sign in to comment.