Skip to content

Commit

Permalink
Merge branch 'master' of github.com:bramstroker/homeassistant-powercalc
Browse files Browse the repository at this point in the history
  • Loading branch information
bramstroker committed Sep 22, 2022
2 parents c2c2945 + a511e04 commit 7c7dbaf
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = E203, E266, E501, W503, F403, F401, WPS305
ignore = E501, W503
max-line-length = 100
max-complexity = 18
select = B,C,E,F,W,T4,B9
4 changes: 2 additions & 2 deletions custom_components/powercalc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
get_power_profile,
has_manufacturer_and_model_information,
)
from .sensors.group import create_group_sensors, update_associated_group_entry
from .sensors.group import update_associated_group_entry
from .strategy.factory import PowerCalculatorStrategyFactory

PLATFORMS = [Platform.SENSOR]
Expand Down Expand Up @@ -330,7 +330,7 @@ async def create_domain_groups(
hass: HomeAssistant, global_config: dict, domains: list[str]
):
"""Create group sensors aggregating all power sensors from given domains"""
_LOGGER.debug(f"Setting up domain based group sensors..")
_LOGGER.debug("Setting up domain based group sensors..")
for domain in domains:
if domain not in hass.data[DOMAIN].get(DATA_DOMAIN_ENTITIES):
_LOGGER.error(f"Cannot setup group for domain {domain}, no entities found")
Expand Down
2 changes: 2 additions & 0 deletions custom_components/powercalc/aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
MANUFACTURER_FEIBIT = "Feibit Inc co. "
MANUFACTURER_GREENWAVE = "GreenWave Reality Inc."
MANUFACTURER_FIBARO = "Fibargroup"
MANUFACTURER_3A_SMARTHOME = "3A Smart Home DE"

MANUFACTURER_ALIASES = {
"Philips": MANUFACTURER_SIGNIFY,
Expand Down Expand Up @@ -44,4 +45,5 @@
MANUFACTURER_WIZ: "wiz",
MANUFACTURER_GREENWAVE: "greenwave",
MANUFACTURER_FIBARO: "fibaro",
MANUFACTURER_3A_SMARTHOME: "3a smarthome",
}
62 changes: 41 additions & 21 deletions custom_components/powercalc/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import copy
import logging
from audioop import mul
from typing import Any

import voluptuous as vol
Expand All @@ -22,11 +21,12 @@
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowHandler, FlowResult
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import selector

from .common import SourceEntity, create_source_entity
from .const import (
CONF_CALCULATION_ENABLED_CONDITION,
CONF_CALIBRATE,
CONF_CREATE_ENERGY_SENSOR,
CONF_CREATE_UTILITY_METERS,
Expand All @@ -49,7 +49,6 @@
CONF_POWER_TEMPLATE,
CONF_SENSOR_TYPE,
CONF_STANDBY_POWER,
CONF_START_TIME,
CONF_STATES_POWER,
CONF_SUB_GROUPS,
CONF_SUB_PROFILE,
Expand Down Expand Up @@ -160,6 +159,10 @@
{vol.Optional(CONF_CONFIRM_AUTODISCOVERED_MODEL, default=True): bool}
)

SCHEMA_POWER_ADVANCED = vol.Schema(
{vol.Optional(CONF_CALCULATION_ENABLED_CONDITION): selector.TemplateSelector()}
)

SCHEMA_GROUP = vol.Schema(
{
vol.Required(CONF_NAME): str,
Expand All @@ -175,7 +178,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

def __init__(self):
"""Initialize options flow."""
self.sensor_config: dict[str, Any] = dict()
self.sensor_config: dict[str, Any] = {}
self.selected_sensor_type: str | None = None
self.name: str | None = None
self.source_entity: SourceEntity | None = None
Expand Down Expand Up @@ -281,7 +284,7 @@ async def async_step_fixed(self, user_input: dict[str, str] = None) -> FlowResul
self.sensor_config.update({CONF_FIXED: user_input})
errors = await self.validate_strategy_config()
if not errors:
return self.create_config_entry()
return await self.async_step_power_advanced()

return self.async_show_form(
step_id="fixed",
Expand All @@ -295,7 +298,7 @@ async def async_step_linear(self, user_input: dict[str, str] = None) -> FlowResu
self.sensor_config.update({CONF_LINEAR: user_input})
errors = await self.validate_strategy_config()
if not errors:
return self.create_config_entry()
return await self.async_step_power_advanced()

return self.async_show_form(
step_id="linear",
Expand All @@ -309,7 +312,7 @@ async def async_step_wled(self, user_input: dict[str, str] = None) -> FlowResult
self.sensor_config.update({CONF_WLED: user_input})
errors = await self.validate_strategy_config()
if not errors:
return self.create_config_entry()
return await self.async_step_power_advanced()

return self.async_show_form(
step_id="wled",
Expand All @@ -321,7 +324,7 @@ async def async_step_lut(self, user_input: dict[str, str] = None) -> FlowResult:
"""Try to autodiscover manufacturer/model first. Ask the user to confirm this or forward to manual configuration"""
if user_input is not None:
if user_input.get(CONF_CONFIRM_AUTODISCOVERED_MODEL):
return self.create_config_entry()
return await self.async_step_power_advanced()

return await self.async_step_lut_manufacturer()

Expand Down Expand Up @@ -381,7 +384,7 @@ async def async_step_lut_model(
return await self.async_step_lut_subprofile()
errors = await self.validate_strategy_config()
if not errors:
return self.create_config_entry()
return await self.async_step_power_advanced()

return self.async_show_form(
step_id="lut_model",
Expand All @@ -404,7 +407,7 @@ async def async_step_lut_subprofile(
self.sensor_config[CONF_MODEL] = model
errors = await self.validate_strategy_config()
if not errors:
return self.create_config_entry()
return await self.async_step_power_advanced()

model_info = ModelInfo(
self.sensor_config.get(CONF_MANUFACTURER),
Expand All @@ -416,6 +419,20 @@ async def async_step_lut_subprofile(
errors=errors,
)

async def async_step_power_advanced(
self, user_input: dict[str, str] = None
) -> FlowResult:
errors = {}
if user_input is not None:
self.sensor_config.update(user_input)
return self.create_config_entry()

return self.async_show_form(
step_id="power_advanced",
data_schema=SCHEMA_POWER_ADVANCED,
errors=errors,
)

async def validate_strategy_config(self) -> dict:
strategy_name = self.sensor_config.get(CONF_MODE)
strategy = await _create_strategy_object(
Expand Down Expand Up @@ -486,17 +503,18 @@ async def save_options(self, user_input: dict[str, Any] | None = None) -> dict:
self.current_config.update({CONF_DAILY_FIXED_ENERGY: daily_energy_config})

if self.sensor_type == SensorType.VIRTUAL_POWER:
self.current_config.update(
{
CONF_CREATE_ENERGY_SENSOR: user_input.get(
CONF_CREATE_ENERGY_SENSOR
),
CONF_CREATE_UTILITY_METERS: user_input.get(
CONF_CREATE_UTILITY_METERS
),
CONF_STANDBY_POWER: user_input.get(CONF_STANDBY_POWER),
}
generic_option_schema = SCHEMA_POWER_OPTIONS.extend(
SCHEMA_POWER_ADVANCED.schema
)
generic_options = {}
for key, val in generic_option_schema.schema.items():
if isinstance(key, vol.Marker):
key = key.schema
if key in user_input:
generic_options[key] = user_input.get(key)

self.current_config.update(generic_options)

strategy = self.current_config.get(CONF_MODE)

strategy_options = _build_strategy_config(
Expand Down Expand Up @@ -530,7 +548,9 @@ def build_options_schema(self) -> vol.Schema:
if self.sensor_type == SensorType.VIRTUAL_POWER:
strategy: str = self.current_config.get(CONF_MODE)
strategy_schema = _get_strategy_schema(strategy, self.source_entity_id)
data_schema = SCHEMA_POWER_OPTIONS.extend(strategy_schema.schema)
data_schema = SCHEMA_POWER_OPTIONS.extend(strategy_schema.schema).extend(
SCHEMA_POWER_ADVANCED.schema
)
strategy_options = self.current_config.get(strategy) or {}

if self.sensor_type == SensorType.DAILY_ENERGY:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/powercalc/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class UnitPrefix(StrEnum):
DEFAULT_UPDATE_FREQUENCY = timedelta(minutes=10)
DEFAULT_POWER_NAME_PATTERN = "{} power"
DEFAULT_POWER_SENSOR_PRECISION = 2
DEFAULT_ENERGY_INTEGRATION_METHOD = ENERGY_INTEGRATION_METHOD_TRAPEZODIAL
DEFAULT_ENERGY_INTEGRATION_METHOD = ENERGY_INTEGRATION_METHOD_LEFT
DEFAULT_ENERGY_NAME_PATTERN = "{} energy"
DEFAULT_ENERGY_SENSOR_PRECISION = 4
DEFAULT_ENTITY_CATEGORY = ENTITY_CATEGORY_NONE
Expand Down
18 changes: 18 additions & 0 deletions custom_components/powercalc/data/signify/LOM001/model.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"measure_description": "Manually measured. Copied from LOM002 as suggested in https://github.com/bramstroker/homeassistant-powercalc/discussions/1093",
"measure_method": "manual",
"measure_device": "Shelly Plug S",
"name": "Hue smart plug LOM001",
"standby_power": 0.23,
"sensor_config": {
"power_sensor_naming": "{} Device Power",
"energy_sensor_naming": "{} Device Energy"
},
"device_type": "smart_switch",
"supported_modes": [
"fixed"
],
"fixed_config": {
"power": 0.91
}
}
2 changes: 1 addition & 1 deletion custom_components/powercalc/power_profile/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, hass: HomeAssistant):
)
if os.path.exists(dir)
]
self._profiles: dict[str, list[PowerProfile]] = dict()
self._profiles: dict[str, list[PowerProfile]] = {}

def factory(hass: HomeAssistant) -> ProfileLibrary:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ async def autodiscover_model(
device_registry = dr.async_get(hass)
device_entry = device_registry.async_get(entity_entry.device_id)
model_id = device_entry.model
if match := re.search("\(([^\(\)]+)\)$", str(device_entry.model)):
if match := re.search(r"\(([^\(\)]+)\)$", str(device_entry.model)):
model_id = match.group(1)

manufacturer = device_entry.manufacturer
Expand Down
2 changes: 1 addition & 1 deletion custom_components/powercalc/power_profile/power_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def get_model_directory(self, root_only: bool = False) -> str:

def get_sub_profiles(self) -> list[str]:
"""Get listing op possible sub profiles"""
return sorted(list(next(os.walk(self.get_model_directory(True)))[1]))
return sorted(next(os.walk(self.get_model_directory(True)))[1])

def supports(self, model: str) -> bool:
model = model.lower().replace("#slash#", "/")
Expand Down
14 changes: 9 additions & 5 deletions custom_components/powercalc/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.utility_meter import DEFAULT_OFFSET, max_28_days
from homeassistant.components.utility_meter import max_28_days
from homeassistant.components.utility_meter.const import METER_TYPES
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand Down Expand Up @@ -68,7 +68,6 @@
CONF_CALCULATION_ENABLED_CONDITION,
CONF_CALIBRATE,
CONF_CREATE_ENERGY_SENSOR,
CONF_CREATE_ENERGY_SENSORS,
CONF_CREATE_GROUP,
CONF_CREATE_UTILITY_METERS,
CONF_CUSTOM_MODEL_DIRECTORY,
Expand Down Expand Up @@ -236,7 +235,7 @@ def build_nested_configuration_schema(schema: dict, iteration: int = 0) -> dict:

SENSOR_CONFIG = build_nested_configuration_schema(SENSOR_CONFIG)

PLATFORM_SCHEMA: Final = vol.All(
PLATFORM_SCHEMA: Final = vol.All( # noqa: F811
cv.has_at_least_one_key(
CONF_ENTITY_ID, CONF_ENTITIES, CONF_INCLUDE, CONF_DAILY_FIXED_ENERGY
),
Expand Down Expand Up @@ -367,6 +366,11 @@ def convert_config_entry_to_sensor_config(config_entry: ConfigEntry) -> dict[str

sensor_config[CONF_LINEAR] = linear_config

if CONF_CALCULATION_ENABLED_CONDITION in sensor_config:
sensor_config[CONF_CALCULATION_ENABLED_CONDITION] = Template(
sensor_config[CONF_CALCULATION_ENABLED_CONDITION]
)

return sensor_config


Expand Down Expand Up @@ -469,7 +473,7 @@ async def create_sensors(
)
elif not sensor_configs:
raise SensorConfigurationError(
f"Could not resolve any entities for non-group sensor"
"Could not resolve any entities for non-group sensor"
)

# Create group sensors (power, energy, utility)
Expand All @@ -487,7 +491,7 @@ async def create_sensors(
return EntitiesBucket(new=new_sensors, existing=existing_sensors)


async def create_individual_sensors(
async def create_individual_sensors( # noqa: C901
hass: HomeAssistant,
sensor_config: dict,
context: CreationContext,
Expand Down
20 changes: 8 additions & 12 deletions custom_components/powercalc/sensors/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, State, CoreState, callback
from homeassistant.core import CoreState, HomeAssistant, State, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.restore_state import RestoreEntity
Expand Down Expand Up @@ -82,17 +82,13 @@ def _get_filtered_entity_ids_by_class(
filters = default_filters.copy()
filters.append(lambda elm: not isinstance(elm, GroupedSensor))
filters.append(lambda elm: isinstance(elm, class_name))
return list(
map(
lambda x: x.entity_id,
list(
filter(
lambda x: all(f(x) for f in filters),
all_entities,
)
),
return [
x.entity_id
for x in filter(
lambda x: all(f(x) for f in filters),
all_entities,
)
)
]

group_sensors = []

Expand Down Expand Up @@ -251,7 +247,7 @@ def create_grouped_power_sensor(
hass, sensor_config, name=group_name, unique_id=unique_id
)

_LOGGER.debug(f"Creating grouped power sensor: %s (entity_id=%s)", name, entity_id)
_LOGGER.debug("Creating grouped power sensor: %s (entity_id=%s)", name, entity_id)

return GroupedPowerSensor(
name=name,
Expand Down
9 changes: 2 additions & 7 deletions custom_components/powercalc/sensors/power.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import DiscoveryInfoType, StateType

from ..common import SourceEntity, get_merged_sensor_configuration
from ..common import SourceEntity
from ..const import (
ATTR_CALCULATION_MODE,
ATTR_ENERGY_SENSOR_ENTITY_ID,
Expand Down Expand Up @@ -59,12 +59,7 @@
OFF_STATES,
CalculationStrategy,
)
from ..errors import (
ModelNotSupported,
SensorConfigurationError,
StrategyConfigurationError,
UnsupportedMode,
)
from ..errors import ModelNotSupported, StrategyConfigurationError, UnsupportedMode
from ..power_profile.model_discovery import get_power_profile
from ..power_profile.power_profile import PowerProfile
from ..strategy.factory import PowerCalculatorStrategyFactory
Expand Down
Loading

0 comments on commit 7c7dbaf

Please sign in to comment.