Skip to content

Commit

Permalink
Merge 8d3e4d0 into 7eec019
Browse files Browse the repository at this point in the history
  • Loading branch information
bramstroker committed Dec 29, 2022
2 parents 7eec019 + 8d3e4d0 commit e658e67
Show file tree
Hide file tree
Showing 13 changed files with 753 additions and 729 deletions.
226 changes: 4 additions & 222 deletions custom_components/powercalc/__init__.py
Expand Up @@ -3,40 +3,28 @@
from __future__ import annotations

import logging
import re
from typing import Optional

import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.entity_registry as er
import voluptuous as vol
from awesomeversion.awesomeversion import AwesomeVersion
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.utility_meter import DEFAULT_OFFSET, max_28_days
from homeassistant.components.utility_meter.const import METER_TYPES
from homeassistant.config_entries import (
SOURCE_INTEGRATION_DISCOVERY,
SOURCE_USER,
ConfigEntry,
ConfigEntryState,
)
from homeassistant.const import (
CONF_DOMAIN,
CONF_ENTITIES,
CONF_ENTITY_ID,
CONF_NAME,
CONF_PLATFORM,
CONF_SCAN_INTERVAL,
CONF_UNIQUE_ID,
EVENT_HOMEASSISTANT_STARTED,
Platform,
)
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import discovery, discovery_flow
from homeassistant.helpers.typing import ConfigType
from homeassistant.core import HomeAssistant
from homeassistant.helpers.discovery import async_load_platform

from .aliases import MANUFACTURER_WLED
from .common import SourceEntity, create_source_entity, validate_name_pattern
from .const import (
CONF_CREATE_DOMAIN_GROUPS,
Expand Down Expand Up @@ -89,12 +77,7 @@
SensorType,
UnitPrefix,
)
from .errors import ModelNotSupported
from .power_profile.model_discovery import (
PowerProfile,
autodiscover_model,
get_power_profile,
)
from .discovery import DiscoveryManager
from .power_profile.power_profile import DEVICE_DOMAINS
from .sensors.group import (
remove_group_from_power_sensor_entry,
Expand Down Expand Up @@ -294,7 +277,7 @@ async def create_domain_groups(
domain_entities = hass.data[DOMAIN].get(DATA_DOMAIN_ENTITIES)[domain]

hass.async_create_task(
discovery.async_load_platform(
async_load_platform(
hass,
SENSOR_DOMAIN,
DOMAIN,
Expand All @@ -306,204 +289,3 @@ async def create_domain_groups(
global_config,
)
)


class DiscoveryManager:
"""
This class is responsible for scanning the HA instance for entities and their manufacturer / model info
It checks if any of these devices is supported in the powercalc library
When entities are found it will dispatch a discovery flow, so the user can add them to their HA instance
"""

def __init__(self, hass: HomeAssistant, ha_config: ConfigType):
self.hass = hass
self.ha_config = ha_config
self.manually_configured_entities: list[str] | None = None

async def start_discovery(self):
"""Start the discovery procedure"""

_LOGGER.debug("Start auto discovering entities")
entity_registry = er.async_get(self.hass)
for entity_entry in list(entity_registry.entities.values()):
if entity_entry.disabled:
continue

if entity_entry.domain not in DEVICE_DOMAINS.values():
continue

has_user_config = self._is_user_configured(entity_entry.entity_id)
if has_user_config:
_LOGGER.debug(
"%s: Entity is manually configured, skipping auto configuration",
entity_entry.entity_id,
)
continue

model_info = await autodiscover_model(self.hass, entity_entry)
if not model_info:
continue

source_entity = await create_source_entity(
entity_entry.entity_id, self.hass
)

if (
model_info.manufacturer == MANUFACTURER_WLED
and entity_entry.domain == LIGHT_DOMAIN
and not re.search(
"master|segment",
str(entity_entry.original_name),
flags=re.IGNORECASE,
)
):
self._init_entity_discovery(
source_entity,
power_profile=None,
extra_discovery_data={
CONF_MODE: CalculationStrategy.WLED,
CONF_MANUFACTURER: model_info.manufacturer,
CONF_MODEL: model_info.model,
},
)
continue

try:
power_profile = await get_power_profile(
self.hass, {}, model_info=model_info
)
if not power_profile:
continue
except ModelNotSupported:
_LOGGER.debug(
"%s: Model not found in library, skipping discovery",
entity_entry.entity_id,
)
continue

if not power_profile.is_entity_domain_supported(source_entity.domain):
continue

self._init_entity_discovery(source_entity, power_profile, {})

_LOGGER.debug("Done auto discovering entities")

@callback
def _init_entity_discovery(
self,
source_entity: SourceEntity,
power_profile: PowerProfile | None,
extra_discovery_data: Optional[dict],
):
"""Dispatch the discovery flow for a given entity"""
existing_entries = [
entry
for entry in self.hass.config_entries.async_entries(DOMAIN)
if entry.unique_id == source_entity.unique_id
]
if existing_entries:
_LOGGER.debug(
f"{source_entity.entity_id}: Already setup with discovery, skipping new discovery"
)
return

discovery_data = {
CONF_UNIQUE_ID: source_entity.unique_id,
CONF_NAME: source_entity.name,
CONF_ENTITY_ID: source_entity.entity_id,
DISCOVERY_SOURCE_ENTITY: source_entity,
}

if power_profile:
discovery_data[DISCOVERY_POWER_PROFILE] = power_profile
discovery_data[CONF_MANUFACTURER] = power_profile.manufacturer
discovery_data[CONF_MODEL] = power_profile.model

if extra_discovery_data:
discovery_data.update(extra_discovery_data)

discovery_flow.async_create_flow(
self.hass,
DOMAIN,
context={"source": SOURCE_INTEGRATION_DISCOVERY},
data=discovery_data,
)

# Code below if for legacy discovery routine, will be removed somewhere in the future
if power_profile and not power_profile.is_additional_configuration_required:
discovery_info = {
CONF_ENTITY_ID: source_entity.entity_id,
DISCOVERY_SOURCE_ENTITY: source_entity,
DISCOVERY_POWER_PROFILE: power_profile,
DISCOVERY_TYPE: PowercalcDiscoveryType.LIBRARY,
}
self.hass.async_create_task(
discovery.async_load_platform(
self.hass, SENSOR_DOMAIN, DOMAIN, discovery_info, self.ha_config
)
)

def _is_user_configured(self, entity_id: str) -> bool:
"""
Check if user have setup powercalc sensors for a given entity_id.
Either with the YAML or GUI method.
"""
if not self.manually_configured_entities:
self.manually_configured_entities = (
self._load_manually_configured_entities()
)

return entity_id in self.manually_configured_entities

def _load_manually_configured_entities(self) -> list[str]:
"""Looks at the YAML and GUI config entries for all the configured entity_id's"""
entities = []

# Find entity ids in yaml config
if SENSOR_DOMAIN in self.ha_config:
sensor_config = self.ha_config.get(SENSOR_DOMAIN)
platform_entries = [
item
for item in sensor_config
if isinstance(item, dict) and item.get(CONF_PLATFORM) == DOMAIN
]
for entry in platform_entries:
entities.extend(self._find_entity_ids_in_yaml_config(entry))

# Add entities from existing config entries
entities.extend(
[
entry.data.get(CONF_ENTITY_ID)
for entry in self.hass.config_entries.async_entries(DOMAIN)
if entry.source == SOURCE_USER
]
)

return entities

def _find_entity_ids_in_yaml_config(self, search_dict: dict):
"""
Takes a dict with nested lists and dicts,
and searches all dicts for a key of the field
provided.
"""
found_entity_ids = []

for key, value in search_dict.items():

if key == "entity_id":
found_entity_ids.append(value)

elif isinstance(value, dict):
results = self._find_entity_ids_in_yaml_config(value)
for result in results:
found_entity_ids.append(result)

elif isinstance(value, list):
for item in value:
if isinstance(item, dict):
results = self._find_entity_ids_in_yaml_config(item)
for result in results:
found_entity_ids.append(result)

return found_entity_ids
7 changes: 4 additions & 3 deletions custom_components/powercalc/config_flow.py
Expand Up @@ -69,9 +69,10 @@
CalculationStrategy,
SensorType,
)
from .discovery import autodiscover_model
from .errors import ModelNotSupported, StrategyConfigurationError
from .power_profile.library import ModelInfo, ProfileLibrary
from .power_profile.model_discovery import get_power_profile
from .power_profile.factory import get_power_profile
from .power_profile.power_profile import PowerProfile
from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY
from .strategy.factory import PowerCalculatorStrategyFactory
Expand Down Expand Up @@ -446,7 +447,7 @@ async def async_step_library(self, user_input: dict[str, str] = None) -> FlowRes
if self.source_entity.entity_entry and self.power_profile is None:
try:
self.power_profile = await get_power_profile(
self.hass, {}, self.source_entity.entity_entry
self.hass, {}, await autodiscover_model(self.hass, self.source_entity.entity_entry)
)
except ModelNotSupported:
self.power_profile = None
Expand Down Expand Up @@ -626,7 +627,7 @@ async def async_step_init(
self.current_config.get(CONF_MODEL),
)
self.power_profile = await get_power_profile(
self.hass, {}, None, model_info
self.hass, {}, model_info
)
if self.power_profile and self.power_profile.needs_fixed_config:
self.strategy = CalculationStrategy.FIXED
Expand Down

0 comments on commit e658e67

Please sign in to comment.