Skip to content

Commit

Permalink
Merge be88c1e into dc28d3f
Browse files Browse the repository at this point in the history
  • Loading branch information
bramstroker committed Apr 23, 2023
2 parents dc28d3f + be88c1e commit 0992a55
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 26 deletions.
19 changes: 19 additions & 0 deletions custom_components/powercalc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
DATA_CONFIGURED_ENTITIES,
DATA_DISCOVERED_ENTITIES,
DATA_DOMAIN_ENTITIES,
DATA_STANDBY_POWER_SENSORS,
DATA_USED_UNIQUE_IDS,
DEFAULT_ENERGY_INTEGRATION_METHOD,
DEFAULT_ENERGY_NAME_PATTERN,
Expand Down Expand Up @@ -193,6 +194,7 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
DATA_DOMAIN_ENTITIES: {},
DATA_DISCOVERED_ENTITIES: {},
DATA_USED_UNIQUE_IDS: [],
DATA_STANDBY_POWER_SENSORS: {},
}

if domain_config.get(CONF_ENABLE_AUTODISCOVERY):
Expand All @@ -213,6 +215,23 @@ async def _create_domain_groups(event: None):
_create_domain_groups,
)

async def _create_standby_group(event: None):
hass.async_create_task(
async_load_platform(
hass,
SENSOR_DOMAIN,
DOMAIN,
{
DISCOVERY_TYPE: PowercalcDiscoveryType.STANDBY_GROUP
},
domain_config,
)
)
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STARTED,
_create_standby_group,
)

return True


Expand Down
3 changes: 2 additions & 1 deletion custom_components/powercalc/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ async def create_source_entity(entity_id: str, hass: HomeAssistant) -> SourceEnt
"""Create object containing all information about the source entity"""

if entity_id == DUMMY_ENTITY_ID:
domain, object_id = split_entity_id(DUMMY_ENTITY_ID)
return SourceEntity(
object_id=DUMMY_ENTITY_ID, entity_id=DUMMY_ENTITY_ID, domain=DUMMY_ENTITY_ID
object_id=object_id, entity_id=DUMMY_ENTITY_ID, domain=domain
)

source_entity_domain, source_object_id = split_entity_id(entity_id)
Expand Down
4 changes: 4 additions & 0 deletions custom_components/powercalc/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
DATA_DOMAIN_ENTITIES = "domain_entities"
DATA_USED_UNIQUE_IDS = "used_unique_ids"
DATA_PROFILE_LIBRARY = "profile_library"
DATA_STANDBY_POWER_SENSORS = "standby_power_sensors"

ENTRY_DATA_ENERGY_ENTITY = "_energy_entity"
ENTRY_DATA_POWER_ENTITY = "_power_entity"
Expand Down Expand Up @@ -151,6 +152,8 @@ class UnitPrefix(StrEnum):
SERVICE_INCREASE_DAILY_ENERGY = "increase_daily_energy"
SERVICE_CALIBRATE_UTILITY_METER = "calibrate_utility_meter"

SIGNAL_POWER_SENSOR_STATE_CHANGE = "powercalc_power_sensor_state_change"

MODE_LUT = "lut"
MODE_LINEAR = "linear"
MODE_FIXED = "fixed"
Expand Down Expand Up @@ -178,4 +181,5 @@ class SensorType(StrEnum):

class PowercalcDiscoveryType(StrEnum):
DOMAIN_GROUP = "domain_group"
STANDBY_GROUP = "standby_group"
LIBRARY = "library"
31 changes: 12 additions & 19 deletions custom_components/powercalc/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import homeassistant.helpers.device_registry as dr
import homeassistant.helpers.entity_registry as er
import voluptuous as vol
from homeassistant.helpers.entity import Entity
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.components.utility_meter import max_28_days
Expand Down Expand Up @@ -124,9 +125,10 @@
from .sensors.energy import EnergySensor, create_energy_sensor
from .sensors.group import (
add_to_associated_group,
create_group_sensors,
create_domain_group_sensor, create_group_sensors,
create_group_sensors_from_config_entry,
)
from .sensors.group_standby import create_general_standby_sensors
from .sensors.power import VirtualPowerSensor, create_power_sensor
from .sensors.utility_meter import create_utility_meters
from .strategy.fixed import CONFIG_SCHEMA as FIXED_SCHEMA
Expand Down Expand Up @@ -449,21 +451,12 @@ async def create_sensors(

global_config = hass.data[DOMAIN][DOMAIN_CONFIG]

# Handle setup of domain groups
if (
discovery_info
and discovery_info[DISCOVERY_TYPE] == PowercalcDiscoveryType.DOMAIN_GROUP
):
domain = discovery_info[CONF_DOMAIN]
sensor_config = global_config.copy()
sensor_config[
CONF_UNIQUE_ID
] = f"powercalc_domaingroup_{discovery_info[CONF_DOMAIN]}"
return EntitiesBucket(
new=await create_group_sensors(
f"All {domain}", sensor_config, discovery_info[CONF_ENTITIES], hass
)
)
# Handle setup of domain groups and general standby power group
if discovery_info:
if discovery_info[DISCOVERY_TYPE] == PowercalcDiscoveryType.DOMAIN_GROUP:
return EntitiesBucket(new=await create_domain_group_sensor(hass, discovery_info, global_config))
if discovery_info[DISCOVERY_TYPE] == PowercalcDiscoveryType.STANDBY_GROUP:
return EntitiesBucket(new=await create_general_standby_sensors(hass, global_config))

# Setup a power sensor for one single appliance. Either by manual configuration or discovery
if CONF_ENTITIES not in config and CONF_INCLUDE not in config:
Expand Down Expand Up @@ -532,7 +525,7 @@ async def create_sensors(
await create_group_sensors(
config.get(CONF_CREATE_GROUP),
get_merged_sensor_configuration(global_config, config, validate=False),
entities_to_add.new + entities_to_add.existing,
entities_to_add.all(),
hass=hass,
)
)
Expand Down Expand Up @@ -721,8 +714,8 @@ async def is_auto_configurable(

@dataclass
class EntitiesBucket:
new: list[BaseEntity] = field(default_factory=list)
existing: list[BaseEntity] = field(default_factory=list)
new: list[Entity] = field(default_factory=list)
existing: list[Entity] = field(default_factory=list)

def extend_items(self, bucket: EntitiesBucket):
self.new.extend(bucket.new)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/powercalc/sensors/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async def create_energy_sensor(
if unit_prefix == UnitPrefix.NONE:
unit_prefix = None

_LOGGER.debug("Creating energy sensor: %s", name)
_LOGGER.debug("Creating energy sensor: %s, %s", name, entity_id)
return VirtualEnergySensor(
source_entity=power_sensor.entity_id,
unique_id=unique_id,
Expand Down
66 changes: 63 additions & 3 deletions custom_components/powercalc/sensors/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
SensorEntity,
SensorStateClass,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_UNIT_OF_MEASUREMENT,
CONF_DOMAIN,
CONF_ENTITIES,
CONF_NAME,
CONF_UNIQUE_ID,
ENERGY_KILO_WATT_HOUR,
Expand All @@ -34,6 +38,7 @@
async_track_state_change_event,
async_track_time_interval,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.singleton import singleton
Expand All @@ -43,6 +48,7 @@
EnergyConverter,
PowerConverter,
)
from ..common import create_source_entity

from ..const import (
ATTR_ENTITIES,
Expand All @@ -58,10 +64,12 @@
CONF_POWER_SENSOR_PRECISION,
CONF_SENSOR_TYPE,
CONF_SUB_GROUPS,
DATA_STANDBY_POWER_SENSORS,
DOMAIN,
ENTRY_DATA_ENERGY_ENTITY,
DUMMY_ENTITY_ID, ENTRY_DATA_ENERGY_ENTITY,
ENTRY_DATA_POWER_ENTITY,
SERVICE_RESET_ENERGY,
SIGNAL_POWER_SENSOR_STATE_CHANGE,
SensorType,
UnitPrefix,
)
Expand All @@ -72,7 +80,7 @@
generate_power_sensor_entity_id,
generate_power_sensor_name,
)
from .energy import EnergySensor
from .energy import EnergySensor, create_energy_sensor
from .power import PowerSensor
from .utility_meter import create_utility_meters

Expand All @@ -88,7 +96,7 @@
async def create_group_sensors(
group_name: str,
sensor_config: dict[str, Any],
entities: list[BaseEntity],
entities: list[Entity],
hass: HomeAssistant,
filters: list[Callable, None] = None,
) -> list[GroupedSensor]:
Expand Down Expand Up @@ -174,6 +182,27 @@ async def create_group_sensors_from_config_entry(
return group_sensors


async def create_general_standby_sensors(hass: HomeAssistant, config: ConfigType) -> list[Entity]:
sensor_config = config.copy()
power_sensor = StandbyPowerSensor(hass, rounding_digits=sensor_config.get(CONF_POWER_SENSOR_PRECISION))
power_sensor.entity_id = "sensor.all_standby_power"
sensor_config[CONF_NAME] = "All standby"
source_entity = await create_source_entity(DUMMY_ENTITY_ID, hass)
energy_sensor = await create_energy_sensor(hass, sensor_config, power_sensor, source_entity)
return [power_sensor, energy_sensor]


async def create_domain_group_sensor(hass: HomeAssistant, discovery_info: DiscoveryInfoType, config: ConfigType) -> list[Entity]:
domain = discovery_info[CONF_DOMAIN]
sensor_config = config.copy()
sensor_config[
CONF_UNIQUE_ID
] = f"powercalc_domaingroup_{discovery_info[CONF_DOMAIN]}"
return await create_group_sensors(
f"All {domain}", sensor_config, discovery_info[CONF_ENTITIES], hass
)


async def remove_power_sensor_from_associated_groups(
hass: HomeAssistant, config_entry: ConfigEntry
) -> list[ConfigEntry]:
Expand Down Expand Up @@ -602,6 +631,37 @@ def calculate_new_state(self, member_states: list[State]) -> Decimal:
return group_sum


class StandbyPowerSensor(SensorEntity, PowerSensor):
_attr_device_class = SensorDeviceClass.POWER
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_native_unit_of_measurement = POWER_WATT
_attr_has_entity_name = True
_attr_unique_id = "powercalc_standby_group"

@property
def name(self):
"""Name of the entity."""
return "All standby power"

def __init__(self, hass: HomeAssistant, rounding_digits: int = 2):
self.standby_sensors: dict[str, Decimal] = hass.data[DOMAIN][DATA_STANDBY_POWER_SENSORS]
self._rounding_digits = rounding_digits

async def async_added_to_hass(self) -> None:
"""Register state listeners."""
await super().async_added_to_hass()
async_dispatcher_connect(self.hass, SIGNAL_POWER_SENSOR_STATE_CHANGE, self._recalculate)

async def _recalculate(self) -> None:
"""Calculate sum of all power sensors in standby, and update the state of the sensor."""

if self.standby_sensors:
self._attr_native_value = round(sum(self.standby_sensors.values()), self._rounding_digits)
else:
self._attr_native_value = STATE_UNKNOWN
self.async_schedule_update_ha_state(True)


class PreviousStateStore:
@staticmethod
@singleton("powercalc_group_storage")
Expand Down
78 changes: 78 additions & 0 deletions custom_components/powercalc/sensors/group_standby.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from __future__ import annotations

import logging
from decimal import Decimal

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.const import (
CONF_NAME,
POWER_WATT,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from custom_components.powercalc.common import create_source_entity

from custom_components.powercalc.const import (
CONF_POWER_SENSOR_PRECISION,
CONF_CREATE_ENERGY_SENSOR,
DATA_STANDBY_POWER_SENSORS,
DOMAIN,
DUMMY_ENTITY_ID,
SIGNAL_POWER_SENSOR_STATE_CHANGE,
)
from custom_components.powercalc.sensors.energy import create_energy_sensor
from custom_components.powercalc.sensors.power import PowerSensor

_LOGGER = logging.getLogger(__name__)


async def create_general_standby_sensors(hass: HomeAssistant, config: ConfigType) -> list[Entity]:
sensors = []
power_sensor = StandbyPowerSensor(hass, rounding_digits=config.get(CONF_POWER_SENSOR_PRECISION))
sensors.append(power_sensor)
if config.get(CONF_CREATE_ENERGY_SENSOR):
power_sensor.entity_id = "sensor.all_standby_power"
sensor_config = config.copy()
sensor_config[CONF_NAME] = "All standby"
source_entity = await create_source_entity(DUMMY_ENTITY_ID, hass)
energy_sensor = await create_energy_sensor(hass, sensor_config, power_sensor, source_entity)
sensors.append(energy_sensor)
return sensors


class StandbyPowerSensor(SensorEntity, PowerSensor):
_attr_device_class = SensorDeviceClass.POWER
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_native_unit_of_measurement = POWER_WATT
_attr_has_entity_name = True
_attr_unique_id = "powercalc_standby_group"

@property
def name(self):
"""Name of the entity."""
return "All standby power"

def __init__(self, hass: HomeAssistant, rounding_digits: int = 2):
self.standby_sensors: dict[str, Decimal] = hass.data[DOMAIN][DATA_STANDBY_POWER_SENSORS]
self._rounding_digits = rounding_digits

async def async_added_to_hass(self) -> None:
"""Register state listeners."""
await super().async_added_to_hass()
async_dispatcher_connect(self.hass, SIGNAL_POWER_SENSOR_STATE_CHANGE, self._recalculate)

async def _recalculate(self) -> None:
"""Calculate sum of all power sensors in standby, and update the state of the sensor."""

if self.standby_sensors:
self._attr_native_value = round(sum(self.standby_sensors.values()), self._rounding_digits)
else:
self._attr_native_value = STATE_UNKNOWN
self.async_schedule_update_ha_state(True)
Loading

0 comments on commit 0992a55

Please sign in to comment.