Skip to content

Commit

Permalink
HACS update to PowerCalc #89
Browse files Browse the repository at this point in the history
  • Loading branch information
BeardedTinker committed Jul 18, 2022
1 parent d897beb commit 77fa1b2
Show file tree
Hide file tree
Showing 12 changed files with 418 additions and 175 deletions.
8 changes: 8 additions & 0 deletions custom_components/powercalc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
CONF_ENERGY_SENSOR_FRIENDLY_NAMING,
CONF_ENERGY_SENSOR_NAMING,
CONF_ENERGY_SENSOR_PRECISION,
CONF_ENERGY_SENSOR_UNIT_PREFIX,
CONF_POWER_SENSOR_CATEGORY,
CONF_POWER_SENSOR_FRIENDLY_NAMING,
CONF_POWER_SENSOR_NAMING,
Expand All @@ -46,6 +47,7 @@
DATA_CONFIGURED_ENTITIES,
DATA_DISCOVERED_ENTITIES,
DATA_DOMAIN_ENTITIES,
DATA_USED_UNIQUE_IDS,
DEFAULT_ENERGY_INTEGRATION_METHOD,
DEFAULT_ENERGY_NAME_PATTERN,
DEFAULT_ENERGY_SENSOR_PRECISION,
Expand All @@ -61,6 +63,7 @@
ENERGY_INTEGRATION_METHODS,
ENTITY_CATEGORIES,
MIN_HA_VERSION,
UnitPrefix,
)
from .errors import ModelNotSupported
from .model_discovery import get_light_model, has_manufacturer_and_model_information
Expand Down Expand Up @@ -117,6 +120,9 @@
CONF_POWER_SENSOR_PRECISION,
default=DEFAULT_POWER_SENSOR_PRECISION,
): cv.positive_int,
vol.Optional(
CONF_ENERGY_SENSOR_UNIT_PREFIX, default=UnitPrefix.KILO
): vol.In([cls.value for cls in UnitPrefix]),
vol.Optional(CONF_CREATE_DOMAIN_GROUPS, default=[]): vol.All(
cv.ensure_list, [cv.string]
),
Expand Down Expand Up @@ -146,6 +152,7 @@ async def async_setup(hass: HomeAssistantType, config: dict) -> bool:
CONF_ENERGY_SENSOR_NAMING: DEFAULT_ENERGY_NAME_PATTERN,
CONF_ENERGY_SENSOR_PRECISION: DEFAULT_ENERGY_SENSOR_PRECISION,
CONF_ENERGY_SENSOR_CATEGORY: DEFAULT_ENTITY_CATEGORY,
CONF_ENERGY_SENSOR_UNIT_PREFIX: UnitPrefix.KILO,
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
CONF_CREATE_DOMAIN_GROUPS: [],
CONF_CREATE_ENERGY_SENSORS: True,
Expand All @@ -161,6 +168,7 @@ async def async_setup(hass: HomeAssistantType, config: dict) -> bool:
DATA_CONFIGURED_ENTITIES: {},
DATA_DOMAIN_ENTITIES: {},
DATA_DISCOVERED_ENTITIES: [],
DATA_USED_UNIQUE_IDS: [],
}

await autodiscover_entities(config, domain_config, hass)
Expand Down
6 changes: 4 additions & 2 deletions custom_components/powercalc/aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"Yeelight": "yeelight",
"TuYa": "tuya",
"MeLiTec": "melitec",
"WiZ": "wiz",
}

MODEL_DIRECTORY_MAPPING = {
Expand Down Expand Up @@ -54,7 +55,7 @@
"TRADFRIbulbE14WScandleopal470lm": "LED1949C5",
"TRADFRIbulbE14WSglobeopal470lm": "LED2002G5",
"TRADFRIbulbE27WSglobeopal1055lm": "LED2003G10",
"TTRADFRIbulbGU10WS345lm": "LED2005R5",
"TRADFRIbulbGU10WS345lm": "LED2005R5",
"TRADFRI bulb GU10 WW 345lm": "LED2005R5",
"LEPTITER Recessed spot light": "T1820",
},
Expand All @@ -65,6 +66,7 @@
"9290022166": "LCA001",
"929003053401": "LCA001",
"9290024687": "LCA007",
"929002471601": "LCA008",
"929001953101": "LCG002",
"1741430P7": "LCS001",
"1741530P7": "LCS001",
Expand Down Expand Up @@ -92,7 +94,7 @@
"8718699671211": "LWE002",
"9290020399": "LWE002",
"915005106701": "LST002",
"7299355PH":"LST001",
"7299355PH": "LST001",
# US Versions. Alias to EU versions
"LCA005": "LCA001",
"9290022266A": "LCA001",
Expand Down
12 changes: 12 additions & 0 deletions custom_components/powercalc/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from datetime import timedelta

from homeassistant.backports.enum import StrEnum
from homeassistant.components.utility_meter.const import DAILY, MONTHLY, WEEKLY
from homeassistant.const import (
STATE_NOT_HOME,
Expand All @@ -19,6 +20,7 @@
DATA_CONFIGURED_ENTITIES = "configured_entities"
DATA_DISCOVERED_ENTITIES = "discovered_entities"
DATA_DOMAIN_ENTITIES = "domain_entities"
DATA_USED_UNIQUE_IDS = "used_unique_ids"

DUMMY_ENTITY_ID = "sensor.dummy"

Expand All @@ -37,6 +39,7 @@
CONF_ENERGY_SENSOR_NAMING = "energy_sensor_naming"
CONF_ENERGY_SENSOR_FRIENDLY_NAMING = "energy_sensor_friendly_naming"
CONF_ENERGY_SENSOR_PRECISION = "energy_sensor_precision"
CONF_ENERGY_SENSOR_UNIT_PREFIX = "energy_sensor_unit_prefix"
CONF_FIXED = "fixed"
CONF_GROUP = "group"
CONF_GAMMA_CURVE = "gamma_curve"
Expand Down Expand Up @@ -84,6 +87,15 @@
ENERGY_INTEGRATION_METHOD_TRAPEZODIAL,
]


class UnitPrefix(StrEnum):
"""Allowed unit prefixes."""

NONE = "none"
KILO = "k"
MEGA = "M"


ENTITY_CATEGORY_CONFIG = "config"
ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic"
ENTITY_CATEGORY_NONE = None
Expand Down
21 changes: 2 additions & 19 deletions custom_components/powercalc/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,11 @@ class SensorConfigurationError(PowercalcSetupError):
class SensorAlreadyConfiguredError(SensorConfigurationError):
"""Raised when power sensors has already been configured before for the entity"""

def __init__(self, source_entity_id: str, existing_entities: list[SensorEntity]):
self.existing_entities = existing_entities
def __init__(self, source_entity_id: str):
super().__init__(
f"{source_entity_id}: This entity has already configured a power sensor"
f"{source_entity_id}: This entity has already configured a power sensor. When you want to configure it twice make sure to give it a unique_id"
)

def get_existing_entities(self):
return self.existing_entities


class SensorAlreadyConfiguredError(SensorConfigurationError):
"""Raised when power sensors has already been configured before for the entity"""

def __init__(self, source_entity_id: str, existing_entities: list[SensorEntity]):
self.existing_entities = existing_entities
super().__init__(
f"{source_entity_id}: This entity has already configured a power sensor"
)

def get_existing_entities(self):
return self.existing_entities


class StrategyConfigurationError(PowercalcSetupError):
"""Raised when strategy is not setup correctly."""
Expand Down
8 changes: 6 additions & 2 deletions custom_components/powercalc/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
"@bramstroker"
],
"dependencies": [
"select"
"light",
"group",
"template",
"select",
"hue"
],
"documentation": "https://github.com/bramstroker/homeassistant-powercalc",
"domain": "powercalc",
Expand All @@ -17,5 +21,5 @@
"requirements": [
"numpy>=1.21.1"
],
"version": "v0.22.1"
"version": "v0.23.0"
}
23 changes: 14 additions & 9 deletions custom_components/powercalc/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,32 @@ def async_migrate_unique_id(

@callback
def async_migrate_entity_id(
hass, platform: str, unique_id: str, new_entity_id: str
hass,
platform: str,
new_entity_id: str,
unique_id: str | None = None,
old_entity_id: str | None = None,
) -> None:
"""Check if entity with old unique ID exists, and if so migrate it to new ID."""

entity_registry = async_get(hass)
if old_entity_id:
entry = entity_registry.async_get(old_entity_id)
if entry is None:
return
else:
old_entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, unique_id)

existing_entity_id = entity_registry.async_get_entity_id(
platform, DOMAIN, unique_id
)
if existing_entity_id is None or existing_entity_id == new_entity_id:
if old_entity_id is None or old_entity_id == new_entity_id:
return

_LOGGER.debug(
"Migrating entity from old entity ID '%s' to new entity ID '%s'",
existing_entity_id,
old_entity_id,
new_entity_id,
)
try:
entity_registry.async_update_entity(
existing_entity_id, new_entity_id=new_entity_id
)
entity_registry.async_update_entity(old_entity_id, new_entity_id=new_entity_id)
except ValueError as e:
_LOGGER.error(e)
entity_registry.async_remove(new_entity_id)
Expand Down
87 changes: 59 additions & 28 deletions custom_components/powercalc/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
HomeAssistantType,
)

from .common import create_source_entity, validate_name_pattern
from .common import SourceEntity, create_source_entity, validate_name_pattern
from .const import (
CALCULATION_MODES,
CONF_AREA,
Expand All @@ -74,6 +74,7 @@
CONF_ENERGY_SENSOR_CATEGORY,
CONF_ENERGY_SENSOR_ID,
CONF_ENERGY_SENSOR_NAMING,
CONF_ENERGY_SENSOR_UNIT_PREFIX,
CONF_FIXED,
CONF_GROUP,
CONF_IGNORE_UNAVAILABLE_STATE,
Expand All @@ -96,13 +97,15 @@
DATA_CONFIGURED_ENTITIES,
DATA_DISCOVERED_ENTITIES,
DATA_DOMAIN_ENTITIES,
DATA_USED_UNIQUE_IDS,
DISCOVERY_SOURCE_ENTITY,
DOMAIN,
DOMAIN_CONFIG,
DUMMY_ENTITY_ID,
ENERGY_INTEGRATION_METHODS,
ENTITY_CATEGORIES,
SERVICE_RESET_ENERGY,
UnitPrefix,
)
from .errors import (
PowercalcSetupError,
Expand Down Expand Up @@ -177,6 +180,9 @@
vol.Optional(CONF_ENERGY_SENSOR_NAMING): validate_name_pattern,
vol.Optional(CONF_ENERGY_SENSOR_CATEGORY): vol.In(ENTITY_CATEGORIES),
vol.Optional(CONF_ENERGY_INTEGRATION_METHOD): vol.In(ENERGY_INTEGRATION_METHODS),
vol.Optional(CONF_ENERGY_SENSOR_UNIT_PREFIX): vol.In(
[cls.value for cls in UnitPrefix]
),
vol.Optional(CONF_CREATE_GROUP): cv.string,
vol.Optional(CONF_INCLUDE, default={}): vol.Schema(
{
Expand Down Expand Up @@ -317,9 +323,14 @@ async def create_sensors(
for entity_config in config[CONF_ENTITIES]:
# When there are nested entities, combine these with the current entities, resursively
if CONF_ENTITIES in entity_config or CONF_CREATE_GROUP in entity_config:
(child_new_sensors, child_existing_sensors) = await create_sensors(
hass, entity_config
)
try:
(child_new_sensors, child_existing_sensors) = await create_sensors(
hass, entity_config
)
except SensorConfigurationError as err:
_LOGGER.error(err)
continue

new_sensors.extend(child_new_sensors)
existing_sensors.extend(child_existing_sensors)
continue
Expand All @@ -345,8 +356,6 @@ async def create_sensors(
new_sensors.extend(
await create_individual_sensors(hass, merged_sensor_config)
)
except SensorAlreadyConfiguredError as error:
existing_sensors.extend(error.get_existing_entities())
except SensorConfigurationError as error:
_LOGGER.error(error)

Expand Down Expand Up @@ -385,24 +394,20 @@ async def create_individual_sensors(
"""Create entities (power, energy, utility_meters) which track the appliance."""

if discovery_info:
source_entity = discovery_info.get(DISCOVERY_SOURCE_ENTITY)
source_entity: SourceEntity = discovery_info.get(DISCOVERY_SOURCE_ENTITY)
else:
source_entity = await create_source_entity(sensor_config[CONF_ENTITY_ID], hass)

if (
source_entity.entity_id in hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES]
and source_entity.entity_id != DUMMY_ENTITY_ID
):
# Display an error when a power sensor was already configured for the same entity by the user
# No log entry will be shown when the entity was auto discovered, we can silently continue
if not discovery_info:
existing_entities = hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES].get(
source_entity.entity_id
)
raise SensorAlreadyConfiguredError(
source_entity.entity_id, existing_entities
)
return []
if (used_unique_ids := hass.data.get(DATA_USED_UNIQUE_IDS)) is None:
used_unique_ids = hass.data[DATA_USED_UNIQUE_IDS] = []
try:
check_entity_not_already_configured(
sensor_config, source_entity, hass, used_unique_ids
)
except SensorAlreadyConfiguredError as error:
if discovery_info:
return []
raise error

entities_to_add = []

Expand Down Expand Up @@ -438,13 +443,6 @@ async def create_individual_sensors(
await create_utility_meters(hass, energy_sensor, sensor_config)
)

if discovery_info:
hass.data[DOMAIN][DATA_DISCOVERED_ENTITIES].append(source_entity.entity_id)
else:
hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES].update(
{source_entity.entity_id: entities_to_add}
)

if source_entity.entity_entry and source_entity.device_entry:
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STARTED,
Expand All @@ -457,16 +455,49 @@ async def create_individual_sensors(
),
)

# Update several registries
if discovery_info:
hass.data[DOMAIN][DATA_DISCOVERED_ENTITIES].append(source_entity.entity_id)
else:
hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES].update(
{source_entity.entity_id: entities_to_add}
)

if not source_entity.domain in hass.data[DOMAIN][DATA_DOMAIN_ENTITIES]:
hass.data[DOMAIN][DATA_DOMAIN_ENTITIES][source_entity.domain] = []

hass.data[DOMAIN][DATA_DOMAIN_ENTITIES][source_entity.domain].extend(
entities_to_add
)

# Keep track for which unique_id's we generated sensors already
unique_id = sensor_config.get(CONF_UNIQUE_ID) or source_entity.unique_id
if unique_id:
used_unique_ids.append(unique_id)

return entities_to_add


def check_entity_not_already_configured(
sensor_config: dict,
source_entity: SourceEntity,
hass: HomeAssistantType,
used_unique_ids: list[str],
):
if source_entity.entity_id == DUMMY_ENTITY_ID:
return

unique_id = sensor_config.get(CONF_UNIQUE_ID) or source_entity.unique_id
if unique_id and unique_id in used_unique_ids:
raise SensorAlreadyConfiguredError(source_entity.entity_id)

if (
unique_id is None
and source_entity.entity_id in hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES]
):
raise SensorAlreadyConfiguredError(source_entity.entity_id)


def bind_entities_to_devices(hass: HomeAssistantType, entities, device_id: str):
"""Attach all the power/energy sensors to the same device as the source entity"""

Expand Down
Loading

0 comments on commit 77fa1b2

Please sign in to comment.