Skip to content

Commit

Permalink
Merge branch 'master' into feat/dail-energy-start-time
Browse files Browse the repository at this point in the history
  • Loading branch information
bramstroker committed Jul 31, 2022
2 parents 5f141ab + afc94c5 commit e1be562
Show file tree
Hide file tree
Showing 130 changed files with 6,927 additions and 824 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/generate-model-list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: Generate support model list

on:
push:
paths:
- 'model.json'
branches:
- master

Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Test

on:
push:
pull_request:

jobs:
tests:
runs-on: "ubuntu-latest"
name: Run tests
steps:
- name: Check out code
uses: "actions/checkout@v2"
- name: Setup Python
uses: "actions/setup-python@v1"
with:
python-version: "3.10"
- name: Prepare test env
run: bash tests/setup.sh
- name: Run tests
run: |
pip install pytest-cov
pip install coverage
pytest \
-qq \
--timeout=9 \
--durations=10 \
-n auto \
--cov custom_components.powercalc \
--cov-report xml \
-o console_output_style=count \
-p no:sugar \
tests
coverage lcov
- name: Upload Coverage Results
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
path-to-lcov: coverage.lcov
1 change: 1 addition & 0 deletions .github/workflows/validate-model-json.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request_target:
paths:
- '**/model.json'
- 'custom_components/powercalc/data/model_schema.json'

permissions:
issues: write
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
__pycache__

# Auto generated on macOS
.DS_Store
.DS_Store

custom_components/test
41 changes: 35 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
![Downloads](https://img.shields.io/github/downloads/bramstroker/homeassistant-powercalc/total)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
[![Coverage Status](https://coveralls.io/repos/github/bramstroker/homeassistant-powercalc/badge.svg?branch=master)](https://coveralls.io/github/bramstroker/homeassistant-powercalc?branch=master)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=bramstroker_homeassistant-powercalc&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=bramstroker_homeassistant-powercalc)

# :zap: PowerCalc: Home Assistant Virtual Power Sensors
Custom component to calculate estimated power consumption of lights and other appliances.
Expand Down Expand Up @@ -60,7 +62,7 @@ powercalc:

### Setup power sensors

Powercalc has a build-in library of more than 100 light models ([LUT](#lut-mode)), which have been measured and provided by users. See [supported models](docs/supported_models.md).
Powercalc has a build-in library of more than 150 light models ([LUT](#lut-mode)), which have been measured and provided by users. See [supported models](docs/supported_models.md).

Starting from 0.12.0 Powercalc can automatically discover entities in your HA instance which are supported for automatic configuration.
After intallation and restarting HA power and energy sensors should appear. When this is not the case please check the logs for any errors.
Expand All @@ -71,13 +73,15 @@ When your appliance is not supported you have extensive options for manual confi
## Configuration

To manually add virtual sensors for your devices you have to add some configuration to `configuration.yaml`.
Additionally some settings can be applied on global level and will apply to all your virtual power sensors.
After changing the configuration you need to restart HA to get your power sensors to appear.
To manually add virtual sensors for your devices you have to add some configuration to `configuration.yaml`, or you can use the GUI configuration ("Settings" -> "Devices & Services" -> "Add integration" -> "Powercalc") and follow the instructions.

Additionally some settings can be applied on global level and will apply to all your virtual power sensors. This global configuration cannot be configured using the GUI yet.

After changing the configuration you need to restart HA to get your power sensors to appear. This is only necessary for changes in the YAML files.

### Sensor configuration

For each entity you want to create a virtual power sensor for you'll need to add an entry in `configuration.yaml`.
For each entity you want to create a virtual power sensor for you'll need to add an entry in `configuration.yaml` or use the GUI config flow. Not all configuration params listed below are available in the GUI, when you want to use the "advanced" options you need to use YAML configuration.
Each virtual power sensor have it's own configuration possibilities.
They are as follows:

Expand All @@ -101,6 +105,7 @@ They are as follows:
| energy_sensor_category | string | **Optional** | Category for the created energy sensor. See [generic-properties](https://developers.home-assistant.io/docs/core/entity/#generic-properties). |
| energy_sensor_naming | string | **Optional** | Change the name (and id) of the sensors. Use the `{}` placeholder for the entity name of your appliance. When set this will override global setting `energy_sensor_naming` |
| energy_integration_method | string | **Optional** | Integration method for the energy sensor. See [HA docs](https://www.home-assistant.io/integrations/integration/#method) |
| energy_sensor_unit_prefix | string | **Optional** | Unit prefix for the energy sensor. See [HA docs](https://www.home-assistant.io/integrations/integration/#unit_prefix). Set to `none` for to create a Wh sensor |
| mode | string | **Optional** | Calculation mode, one of `lut`, `linear`, `fixed`. The default mode is `lut` |
| multiply_factor | float | **Optional** | Multiplies the calculated power by this number. See [multiply factor](#multiply-factor) |
| multiply_factor_standby | boolean | **Optional** | When set to `true` the `multiply_factor` will also be applied to the standby power |
Expand All @@ -109,6 +114,7 @@ They are as follows:
| wled | object | **Optional** | [WLED mode options](#wled-mode) |
| entities | list | **Optional** | Makes it possible to add multiple entities at once in one powercalc entry. Also enable possibility to create group sensors automatically. See [multiple entities and grouping](#multiple-entities-and-grouping) |
| create_group | string | **Optional** | This setting is only applicable when you also use `entities` setting or `include`. Define a group name here. See [multiple entities and grouping](#multiple-entities-and-grouping) |
| hide_members | boolean | **Optional** | Hide all group members in HA GUI, only applicable when `create_group` is also defined.
| include | object | **Optional** | Use this in combination with `create_group` to automatically include entities from a certain area, group or template. See [Include entities](#dynamically-including-entities)
| power_sensor_id | string | **Optional** | Entity id of an existing power sensor. This can be used to let powercalc create energy sensors and utility meters. This will create no virtual power sensor.
| energy_sensor_id | string | **Optional** | Entity id of an existing energy sensor. Mostly used in conjunction with `power_sensor_id`.
Expand All @@ -135,7 +141,7 @@ All these settings are completely optional. You can skip this section if you don
| Name | Type | Requirement | Default | Description |
| ----------------------------- | ------- | ------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| enable_autodiscovery | boolean | **Optional** | true | Whether you want powercalc to automatically setup power sensors for supported models in your HA instance.
| scan_interval | string | **Optional** | 00:10:00 | Interval at which the sensor state is updated, even when the power value stays the same. Format HH:MM:SS |
| force_update_frequency | string | **Optional** | 00:10:00 | Interval at which the sensor state is updated, even when the power value stays the same. Format HH:MM:SS |
| create_energy_sensors | boolean | **Optional** | true | Let the component automatically create energy sensors (kWh) for every power sensor |
| power_sensor_naming | string | **Optional** | {} power | Change the name of the sensors. Use the `{}` placeholder for the entity name of your appliance. This will also change the entity_id of your sensor |
| power_sensor_friendly_naming | string | **Optional** | | Change the friendly name of the sensors, Use `{}` placehorder for the original entity name. |
Expand All @@ -148,6 +154,7 @@ All these settings are completely optional. You can skip this section if you don
| utility_meter_tariffs | list | **Optional** | Define different tariffs. See [tariffs](https://www.home-assistant.io/integrations/utility_meter/#tariffs). |
| energy_integration_method | string | **Optional** | trapezoid | Integration method for the energy sensor. See [HA docs](https://www.home-assistant.io/integrations/integration/#method) |
| energy_sensor_precision | numeric | **Optional** | 4 | Number of decimals you want for the energy sensors. See [HA docs](https://www.home-assistant.io/integrations/integration/#round) |
| energy_sensor_unit_prefix | string | **Optional** | Unit prefix for the energy sensor. See [HA docs](https://www.home-assistant.io/integrations/integration/#unit_prefix). Set to `none` for to create a Wh sensor |
| create_domain_groups | list | **Optional** | | Create grouped power sensor aggregating all powercalc sensors of given domains, see [Group sensors per domain](#group-sensors-per-domain)

**Example:**
Expand Down Expand Up @@ -318,6 +325,16 @@ sensor:
power: "{{states('input_number.bathroom_watts')}}"
```

When you don't have a source entity or helper (ex. `input_boolean`) to bind on and you just want the power sensor to reflect the template value you can use `sensor.dummy` as the entity_id

```yaml
sensor:
- platform: powercalc
entity_id: sensor.dummy
fixed:
power: "{{states('input_number.bathroom_watts')}}"
```

#### Power per state
The `states_power` setting allows you to specify a power per entity state. This can be useful for example on Sonos devices which have a different power consumption in different states.

Expand Down Expand Up @@ -450,6 +467,18 @@ will create:
- sensor.patio_power (Patio power)
- sensor.patio_kwh_consumed (Patio kWh consumed)

### Friendly naming
This option allows you to separately change only the name (shown in GUI), it will not have effect on the entity id

```yaml
powercalc:
energy_sensor_naming: "{} kwh"
energy_sensor_friendly_naming: "{} Energy consumed
```
will create:
- sensor.patio_kwh (Patio Energy consumed)
### Change name
You can also change the sensor name with the `name` option
Expand Down
Empty file added custom_components/__init__.py
Empty file.
81 changes: 68 additions & 13 deletions custom_components/powercalc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@
from homeassistant.components.switch import DOMAIN as SWITCH_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 ConfigEntry
from homeassistant.const import (
CONF_ENTITY_ID,
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
from homeassistant.helpers import discovery
from homeassistant.helpers.typing import HomeAssistantType

from .common import create_source_entity, validate_name_pattern
from .const import (
Expand All @@ -35,6 +37,8 @@
CONF_ENERGY_SENSOR_FRIENDLY_NAMING,
CONF_ENERGY_SENSOR_NAMING,
CONF_ENERGY_SENSOR_PRECISION,
CONF_ENERGY_SENSOR_UNIT_PREFIX,
CONF_FORCE_UPDATE_FREQUENCY,
CONF_POWER_SENSOR_CATEGORY,
CONF_POWER_SENSOR_FRIENDLY_NAMING,
CONF_POWER_SENSOR_NAMING,
Expand All @@ -46,13 +50,14 @@
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,
DEFAULT_ENTITY_CATEGORY,
DEFAULT_POWER_NAME_PATTERN,
DEFAULT_POWER_SENSOR_PRECISION,
DEFAULT_SCAN_INTERVAL,
DEFAULT_UPDATE_FREQUENCY,
DEFAULT_UTILITY_METER_TYPES,
DISCOVERY_LIGHT_MODEL,
DISCOVERY_SOURCE_ENTITY,
Expand All @@ -61,19 +66,28 @@
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
from .power_profile.model_discovery import (
get_light_model,
has_manufacturer_and_model_information,
)
from .sensors.group import create_group_sensors
from .strategy.factory import PowerCalculatorStrategyFactory

PLATFORMS = [Platform.SENSOR]

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
cv.deprecated(
CONF_SCAN_INTERVAL, replacement_key=CONF_FORCE_UPDATE_FREQUENCY
),
vol.Schema(
{
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
CONF_FORCE_UPDATE_FREQUENCY, default=DEFAULT_UPDATE_FREQUENCY
): cv.time_period,
vol.Optional(
CONF_POWER_SENSOR_NAMING, default=DEFAULT_POWER_NAME_PATTERN
Expand Down Expand Up @@ -117,6 +131,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 All @@ -130,7 +147,7 @@
_LOGGER = logging.getLogger(__name__)


async def async_setup(hass: HomeAssistantType, config: dict) -> bool:
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
if AwesomeVersion(HA_VERSION) < AwesomeVersion(MIN_HA_VERSION):
_LOGGER.critical(
"Your HA version is outdated for this version of powercalc. Minimum required HA version is %s",
Expand All @@ -146,7 +163,8 @@ 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_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
CONF_ENERGY_SENSOR_UNIT_PREFIX: UnitPrefix.KILO,
CONF_FORCE_UPDATE_FREQUENCY: DEFAULT_UPDATE_FREQUENCY,
CONF_CREATE_DOMAIN_GROUPS: [],
CONF_CREATE_ENERGY_SENSORS: True,
CONF_CREATE_UTILITY_METERS: False,
Expand All @@ -160,7 +178,8 @@ async def async_setup(hass: HomeAssistantType, config: dict) -> bool:
DOMAIN_CONFIG: domain_config,
DATA_CONFIGURED_ENTITIES: {},
DATA_DOMAIN_ENTITIES: {},
DATA_DISCOVERED_ENTITIES: [],
DATA_DISCOVERED_ENTITIES: {},
DATA_USED_UNIQUE_IDS: [],
}

await autodiscover_entities(config, domain_config, hass)
Expand All @@ -182,9 +201,43 @@ async def _create_domain_groups(event: None):
return True


async def autodiscover_entities(
config: dict, domain_config: dict, hass: HomeAssistantType
):
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Powercalc integration from a config entry."""
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_update_entry))
return True


async def async_update_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update a given config entry."""
await hass.config_entries.async_reload(entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)

if unload_ok:
used_unique_ids: list[str] = hass.data[DOMAIN][DATA_USED_UNIQUE_IDS]
try:
used_unique_ids.remove(config_entry.unique_id)
except ValueError:
return True

entity_registry = er.async_get(hass)
entries = er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
for entry in entries:
entity_registry.async_remove(entry.entity_id)
_LOGGER.debug(f"Removing {entry.entity_id}")

return unload_ok


async def autodiscover_entities(config: dict, domain_config: dict, hass: HomeAssistant):
"""Discover entities supported for powercalc autoconfiguration in HA instance"""

if not domain_config.get(CONF_ENABLE_AUTODISCOVERY):
Expand All @@ -196,7 +249,7 @@ async def autodiscover_entities(
if entity_entry.disabled:
continue

if not entity_entry.domain in (LIGHT_DOMAIN, SWITCH_DOMAIN):
if entity_entry.domain not in (LIGHT_DOMAIN, SWITCH_DOMAIN):
continue

if not await has_manufacturer_and_model_information(hass, entity_entry):
Expand Down Expand Up @@ -241,6 +294,8 @@ async def autodiscover_entities(


def get_manual_configuration(config: dict, entity_id: str) -> dict | None:
if SENSOR_DOMAIN not in config:
return None
sensor_config = config.get(SENSOR_DOMAIN)
for item in sensor_config:
if item.get(CONF_PLATFORM) == DOMAIN and item.get(CONF_ENTITY_ID) == entity_id:
Expand All @@ -249,14 +304,14 @@ def get_manual_configuration(config: dict, entity_id: str) -> dict | None:


async def create_domain_groups(
hass: HomeAssistantType, global_config: dict, domains: list[str]
hass: HomeAssistant, global_config: dict, domains: list[str]
):
"""Create group sensors aggregating all power sensors from given domains"""
sensor_component = hass.data[SENSOR_DOMAIN]
sensor_config = global_config.copy()
_LOGGER.debug(f"Setting up domain based group sensors..")
for domain in domains:
if not domain in hass.data[DOMAIN].get(DATA_DOMAIN_ENTITIES):
if domain not in hass.data[DOMAIN].get(DATA_DOMAIN_ENTITIES):
_LOGGER.error(f"Cannot setup group for domain {domain}, no entities found")
continue

Expand Down
Loading

0 comments on commit e1be562

Please sign in to comment.