mirror of
https://github.com/Burningstone91/smart-home-setup.git
synced 2022-05-05 21:16:50 +03:00
Update custom component 'powercalc' and adjusted config accordingly
This commit is contained in:
@@ -2,23 +2,35 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant.components.utility_meter.const import (
|
||||
DAILY,
|
||||
METER_TYPES,
|
||||
MONTHLY,
|
||||
WEEKLY,
|
||||
)
|
||||
from homeassistant.const import CONF_SCAN_INTERVAL
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import (
|
||||
CONF_CREATE_ENERGY_SENSORS,
|
||||
CONF_CREATE_UTILITY_METERS,
|
||||
CONF_ENERGY_SENSOR_NAMING,
|
||||
CONF_ENTITY_NAME_PATTERN,
|
||||
CONF_FIXED,
|
||||
CONF_LINEAR,
|
||||
CONF_MAX_POWER,
|
||||
CONF_MAX_WATT,
|
||||
CONF_MIN_POWER,
|
||||
CONF_MIN_WATT,
|
||||
CONF_POWER,
|
||||
CONF_POWER_SENSOR_NAMING,
|
||||
CONF_STATES_POWER,
|
||||
CONF_WATT,
|
||||
CONF_UTILITY_METER_TYPES,
|
||||
DATA_CALCULATOR_FACTORY,
|
||||
DOMAIN,
|
||||
DOMAIN_CONFIG,
|
||||
MODE_FIXED,
|
||||
MODE_LINEAR,
|
||||
MODE_LUT,
|
||||
@@ -30,12 +42,60 @@ from .strategy_interface import PowerCalculationStrategyInterface
|
||||
from .strategy_linear import LinearStrategy
|
||||
from .strategy_lut import LutRegistry, LutStrategy
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
|
||||
DEFAULT_POWER_NAME_PATTERN = "{} power"
|
||||
DEFAULT_ENERGY_NAME_PATTERN = "{} energy"
|
||||
|
||||
|
||||
def validate_name_pattern(value: str) -> str:
|
||||
"""Validate that the naming pattern contains {}."""
|
||||
regex = re.compile(r"\{\}")
|
||||
if not regex.search(value):
|
||||
raise vol.Invalid("Naming pattern must contain {}")
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
|
||||
): cv.time_period,
|
||||
vol.Optional(CONF_ENTITY_NAME_PATTERN): validate_name_pattern,
|
||||
vol.Optional(
|
||||
CONF_POWER_SENSOR_NAMING, default=DEFAULT_POWER_NAME_PATTERN
|
||||
): validate_name_pattern,
|
||||
vol.Optional(
|
||||
CONF_ENERGY_SENSOR_NAMING, default=DEFAULT_ENERGY_NAME_PATTERN
|
||||
): validate_name_pattern,
|
||||
vol.Optional(CONF_CREATE_ENERGY_SENSORS, default=True): cv.boolean,
|
||||
vol.Optional(CONF_CREATE_UTILITY_METERS, default=False): cv.boolean,
|
||||
vol.Optional(
|
||||
CONF_UTILITY_METER_TYPES, default=[DAILY, WEEKLY, MONTHLY]
|
||||
): vol.All(cv.ensure_list, [vol.In(METER_TYPES)]),
|
||||
}
|
||||
),
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: dict) -> bool:
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][DATA_CALCULATOR_FACTORY] = PowerCalculatorStrategyFactory(hass)
|
||||
conf = config.get(DOMAIN) or {
|
||||
CONF_POWER_SENSOR_NAMING: DEFAULT_POWER_NAME_PATTERN,
|
||||
CONF_ENERGY_SENSOR_NAMING: DEFAULT_ENERGY_NAME_PATTERN,
|
||||
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
|
||||
CONF_CREATE_ENERGY_SENSORS: True,
|
||||
CONF_CREATE_UTILITY_METERS: False,
|
||||
}
|
||||
|
||||
hass.data[DOMAIN] = {
|
||||
DATA_CALCULATOR_FACTORY: PowerCalculatorStrategyFactory(hass),
|
||||
DOMAIN_CONFIG: conf,
|
||||
}
|
||||
|
||||
return True
|
||||
|
||||
@@ -70,19 +130,8 @@ class PowerCalculatorStrategyFactory:
|
||||
"""Create the linear strategy"""
|
||||
linear_config = config.get(CONF_LINEAR)
|
||||
|
||||
if linear_config is None:
|
||||
# Below is for BC compatibility
|
||||
if config.get(CONF_MIN_WATT) is not None:
|
||||
_LOGGER.warning(
|
||||
"min_watt is deprecated and will be removed in version 0.3, use linear->min_power"
|
||||
)
|
||||
linear_config = {
|
||||
CONF_MIN_POWER: config.get(CONF_MIN_WATT),
|
||||
CONF_MAX_POWER: config.get(CONF_MAX_WATT),
|
||||
}
|
||||
|
||||
elif light_model is not None:
|
||||
linear_config = light_model.linear_mode_config
|
||||
if linear_config is None and light_model is not None:
|
||||
linear_config = light_model.linear_mode_config
|
||||
|
||||
return LinearStrategy(linear_config, entity_domain)
|
||||
|
||||
@@ -92,13 +141,6 @@ class PowerCalculatorStrategyFactory:
|
||||
if fixed_config is None and light_model is not None:
|
||||
fixed_config = light_model.fixed_mode_config
|
||||
|
||||
# BC compat
|
||||
if fixed_config is None:
|
||||
_LOGGER.warning(
|
||||
"watt is deprecated and will be removed in version 0.3, use fixed->power"
|
||||
)
|
||||
fixed_config = {CONF_POWER: config.get(CONF_WATT)}
|
||||
|
||||
return FixedStrategy(
|
||||
fixed_config.get(CONF_POWER), fixed_config.get(CONF_STATES_POWER)
|
||||
)
|
||||
|
||||
15
home-assistant/custom_components/powercalc/common.py
Executable file
15
home-assistant/custom_components/powercalc/common.py
Executable file
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable, Mapping
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
class SourceEntity(NamedTuple):
|
||||
unique_id: str
|
||||
object_id: str
|
||||
entity_id: str
|
||||
name: str
|
||||
domain: str
|
||||
capabilities: Mapping[str, Any] | None = attr.ib(default=None)
|
||||
@@ -1,17 +1,26 @@
|
||||
"""The Hue Power constants."""
|
||||
|
||||
DOMAIN = "powercalc"
|
||||
DOMAIN_CONFIG = "config"
|
||||
|
||||
DATA_CALCULATOR_FACTORY = "calculator_factory"
|
||||
|
||||
CONF_CALIBRATE = "calibrate"
|
||||
CONF_CREATE_ENERGY_SENSOR = "create_energy_sensor"
|
||||
CONF_CREATE_ENERGY_SENSORS = "create_energy_sensors"
|
||||
CONF_CREATE_UTILITY_METERS = "create_utility_meters"
|
||||
CONF_ENERGY_SENSOR_NAMING = "energy_sensor_naming"
|
||||
CONF_ENTITY_NAME_PATTERN = "entity_name_pattern"
|
||||
CONF_FIXED = "fixed"
|
||||
CONF_LINEAR = "linear"
|
||||
CONF_MODEL = "model"
|
||||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_MODE = "mode"
|
||||
CONF_MULTIPLY_FACTOR = "multiply_factor"
|
||||
CONF_MULTIPLY_FACTOR_STANDBY = "multiply_factor_standby"
|
||||
CONF_MIN_WATT = "min_watt"
|
||||
CONF_MAX_WATT = "max_watt"
|
||||
CONF_POWER_SENSOR_NAMING = "power_sensor_naming"
|
||||
CONF_POWER = "power"
|
||||
CONF_MIN_POWER = "min_power"
|
||||
CONF_MAX_POWER = "max_power"
|
||||
@@ -20,14 +29,22 @@ CONF_STATES_POWER = "states_power"
|
||||
CONF_STANDBY_USAGE = "standby_usage"
|
||||
CONF_DISABLE_STANDBY_USAGE = "disable_standby_usage"
|
||||
CONF_CUSTOM_MODEL_DIRECTORY = "custom_model_directory"
|
||||
CONF_UTILITY_METER_TYPES = "utility_meter_types"
|
||||
|
||||
MODE_LUT = "lut"
|
||||
MODE_LINEAR = "linear"
|
||||
MODE_FIXED = "fixed"
|
||||
CALCULATION_MODES = [
|
||||
MODE_FIXED,
|
||||
MODE_LINEAR,
|
||||
MODE_LUT,
|
||||
]
|
||||
|
||||
MANUFACTURER_DIRECTORY_MAPPING = {
|
||||
"IKEA of Sweden": "ikea",
|
||||
"Feibit Inc co. ": "jiawen",
|
||||
"LEDVANCE": "ledvance",
|
||||
"MLI": "mueller-licht",
|
||||
"OSRAM": "osram",
|
||||
"Signify Netherlands B.V.": "signify",
|
||||
}
|
||||
@@ -35,6 +52,8 @@ MANUFACTURER_DIRECTORY_MAPPING = {
|
||||
MODEL_DIRECTORY_MAPPING = {
|
||||
"IKEA of Sweden": {
|
||||
"TRADFRI bulb E14 WS opal 400lm": "LED1536G5",
|
||||
"TRADFRI bulb GU10 WS 400lm": "LED1537R6",
|
||||
"TRADFRI bulb E27 WS opal 980lm": "LED1545G12",
|
||||
"TRADFRI bulb E27 WS clear 950lm": "LED1546G12",
|
||||
"TRADFRI bulb E27 opal 1000lm": "LED1623G12",
|
||||
"TRADFRI bulb E27 CWS opal 600lm": "LED1624G9",
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/belkin/F7C033/brightness.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/belkin/F7C033/brightness.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/belkin/F7C033/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/belkin/F7C033/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "WeMo smart LED bulb",
|
||||
"standby_usage": 0.4,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "RRPM02 (Model); 4897037690801 (Barcode); Reduction Revolution Plug-in Power Meter",
|
||||
"measure_description": "Was not able to measure the standby usage, the value is assumed"
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.44,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured with script made by bramstroker and partially verified manual"
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/ikea/LED1537R6/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/ikea/LED1537R6/color_temp.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/ikea/LED1537R6/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/ikea/LED1537R6/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "TRADFRI bulb GU10 WS 400lm LED1537R6",
|
||||
"standby_usage": 0.44,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured once with old version script made by bramstroker using 6 sec pauses and partially verified and fixed manual"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "TRADFRI bulb E27 WS opal 980lm LED1545G12",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.43,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured with script made by bramstroker and partially verified manual"
|
||||
}
|
||||
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.36,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
|
||||
"measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
|
||||
}
|
||||
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.40,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
|
||||
"measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
|
||||
}
|
||||
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.38,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
|
||||
"measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
|
||||
}
|
||||
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.26,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured with script made by bramstroker and partially verified manual"
|
||||
}
|
||||
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.35,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
|
||||
"measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
|
||||
}
|
||||
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.38,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
|
||||
"measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Tibea Lamp E27 Tuneable White 2000lm",
|
||||
"standby_usage": 0.51,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured twice with script made by bramstroker and partially verified manual"
|
||||
}
|
||||
64
home-assistant/custom_components/powercalc/data/model_schema.json
Executable file
64
home-assistant/custom_components/powercalc/data/model_schema.json
Executable file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "model.json described a light model",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"standby_usage",
|
||||
"supported_modes",
|
||||
"measure_method",
|
||||
"measure_device"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The full name"
|
||||
},
|
||||
"standby_usage": {
|
||||
"type": "number",
|
||||
"description": "Power draw when the light is turned of. When you are not able to measure set to 0.4"
|
||||
},
|
||||
"supported_modes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["lut", "linear", "fixed"]
|
||||
},
|
||||
"description": "Supported calculation modes"
|
||||
},
|
||||
"measure_method": {
|
||||
"type": "string",
|
||||
"enum": ["manual", "script"],
|
||||
"description": "How the light was measured"
|
||||
},
|
||||
"measure_device": {
|
||||
"type": "string",
|
||||
"description": "Device which was used to measure"
|
||||
},
|
||||
"measure_description": {
|
||||
"type": "string",
|
||||
"description": "Add more information about how you measured the light or any remarks"
|
||||
},
|
||||
"linear_config": {
|
||||
"type": "object",
|
||||
"description": "Configuration for linear calculation mode",
|
||||
"properties": {
|
||||
"min_watt": {
|
||||
"type": "number"
|
||||
},
|
||||
"max_watt": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fixed_config": {
|
||||
"type": "object",
|
||||
"description": "Configuration for fixed calculation mode",
|
||||
"properties": {
|
||||
"watt": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "tint GU10 Spot 350lm",
|
||||
"standby_usage": 0.31,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured once with script made by bramstroker using 9 sec pauses and partially verified manual"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name":"LIGHTIFY Indoor Flex RGBW 3P (1.8m)",
|
||||
"standby_usage":0.3,
|
||||
"supported_modes":[
|
||||
"lut"
|
||||
],
|
||||
"measure_device":"Gosund SP111 on Tasmota 9.5.0",
|
||||
"measure_description":"Measure Script for Tasmota",
|
||||
"measure_method":"script"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "Hue White and Color Ambiance A19 E26 (Gen 5)",
|
||||
"name": "Hue White and Color Ambiance A19 E26/E27 (Gen 5)",
|
||||
"standby_usage": 0.33,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LCE002/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCE002/color_temp.csv.gz
Executable file
Binary file not shown.
BIN
home-assistant/custom_components/powercalc/data/signify/LCE002/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCE002/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LCE002/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LCE002/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue White and Color Ambiance E14 Candle w/ BT",
|
||||
"standby_usage": 0.28,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "AVM FRITZ!DECT 200",
|
||||
"measure_description": "Measured with own custom script"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LCF002/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCF002/color_temp.csv.gz
Executable file
Binary file not shown.
BIN
home-assistant/custom_components/powercalc/data/signify/LCF002/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCF002/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LCF002/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LCF002/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name":"Hue Calla Outdoor Pedestal",
|
||||
"standby_usage":0.4,
|
||||
"supported_modes":[
|
||||
"lut"
|
||||
],
|
||||
"measure_device":"Gosund SP111 on Tasmota 9.5.0",
|
||||
"measure_description":"Measure Script for Tasmota",
|
||||
"measure_method":"script"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LCT001/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCT001/color_temp.csv.gz
Executable file
Binary file not shown.
BIN
home-assistant/custom_components/powercalc/data/signify/LCT001/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCT001/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LCT001/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LCT001/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Philips Hue White and Color Ambience (1 gen)",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"standby_usage": 0.22,
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly PM1",
|
||||
"measure_description": "Used HASS based script"
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "Hue White and Color Ambiance Spot GU10",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly plug S",
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository"
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "Hue White and Color Ambiance A19 E26 (Gen 3)",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly plug S",
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository"
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "Hue White and Color Ambiance Candle E12",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly plug S",
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LCT015/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCT015/hs.csv.gz
Executable file
Binary file not shown.
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"name": "Hue White and Color Ambiance A19 E26 (Gen 4)",
|
||||
"name": "Hue White and Color Ambiance A19 E26/E27 (Gen 4)",
|
||||
"standby_usage": 0.47,
|
||||
"measure_device": "unknown",
|
||||
"measure_method": "script",
|
||||
"measure_description": "",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LCT024/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCT024/color_temp.csv.gz
Executable file
Binary file not shown.
BIN
home-assistant/custom_components/powercalc/data/signify/LCT024/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCT024/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LCT024/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LCT024/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue Play",
|
||||
"standby_usage": 0.100,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "TP-Link HS110",
|
||||
"measure_description": "Measured with script made by smonesi (steps: bri 20/sat 40/mired 30/hue 2000) and linearly interpolated to provide more data"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LCT026/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCT026/color_temp.csv.gz
Executable file
Binary file not shown.
BIN
home-assistant/custom_components/powercalc/data/signify/LCT026/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LCT026/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LCT026/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LCT026/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue Go 2.0",
|
||||
"standby_usage": 0.49,
|
||||
"measure_method": "script",
|
||||
"measure_device": "TP-Link HS110",
|
||||
"measure_description": "Measured with measure_v2 script made by smonesi (SAMPLE_COUNT=6, steps: bri 40/sat 40/mired 30/hue 2000) and linearly interpolated to provide more data",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LLC001/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LLC001/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LLC001/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LLC001/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Philips Living Color Iris",
|
||||
"standby_usage": 0.500,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "TP-Link HS110",
|
||||
"measure_description": "Measured with script made by smonesi (steps: bri 50/sat 60/hue 2000) and linearly interpolated to provide more data"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LLC010/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LLC010/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LLC010/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LLC010/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue Iris",
|
||||
"standby_usage": 0.40,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "TP-Link HS110",
|
||||
"measure_description": "Measured with script made by smonesi (steps: bri 50/sat 60/hue 2000) and linearly interpolated to provide more data"
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.29,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured with script made by bramstroker and partially verified manual"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LLC020/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LLC020/color_temp.csv.gz
Executable file
Binary file not shown.
BIN
home-assistant/custom_components/powercalc/data/signify/LLC020/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LLC020/hs.csv.gz
Executable file
Binary file not shown.
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "Hue Go",
|
||||
"standby_usage": 0.290,
|
||||
"supported_modes": [
|
||||
"linear"
|
||||
"lut"
|
||||
],
|
||||
"linear_config": {
|
||||
"min_power": 0,
|
||||
"max_power": 6
|
||||
}
|
||||
}
|
||||
"measure_method": "script",
|
||||
"measure_device": "TP-Link HS110",
|
||||
"measure_description": "Measured with script made by smonesi (steps: bri 20/sat 40/hue 2000/mired 30) and linearly interpolated to provide more data"
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LST004/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LST004/color_temp.csv.gz
Executable file
Binary file not shown.
BIN
home-assistant/custom_components/powercalc/data/signify/LST004/hs.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LST004/hs.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LST004/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LST004/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name":"Hue White and Color Ambiance LED Outdoor Lightstrip (5m)",
|
||||
"standby_usage":0.4,
|
||||
"supported_modes":[
|
||||
"lut"
|
||||
],
|
||||
"measure_device":"Gosund SP111 on Tasmota 9.5.0",
|
||||
"measure_description":"Measure Script for Tasmota",
|
||||
"measure_method":"script"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LTA001/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LTA001/color_temp.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LTA001/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LTA001/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue White Ambiance E27",
|
||||
"standby_usage": 0.29,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "TP-Link HS110",
|
||||
"measure_description": "Measured with script made by smonesi (steps: bri 30/mired 30) and linearly interpolated to provide more data"
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "Hue Being Ceiling Light",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly plug S",
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository"
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LTC002/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LTC002/color_temp.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LTC002/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LTC002/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name":"Hue Fair Ceiling Lamp",
|
||||
"standby_usage":0.4,
|
||||
"supported_modes":[
|
||||
"lut"
|
||||
],
|
||||
"measure_device":"Gosund SP111 on Tasmota 9.5.0",
|
||||
"measure_description":"Measure Script for Tasmota",
|
||||
"measure_method":"script"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LTP003/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LTP003/color_temp.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LTP003/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LTP003/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name":"Hue Fair Pendant",
|
||||
"standby_usage":0.4,
|
||||
"supported_modes":[
|
||||
"lut"
|
||||
],
|
||||
"measure_device":"Gosund SP111 on Tasmota 9.5.0",
|
||||
"measure_description":"Measure Script for Tasmota",
|
||||
"measure_method":"script"
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "Hue White Ambiance A19",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly plug S",
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository"
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.39,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured with script made by bramstroker and partially verified manual"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LTW012/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LTW012/color_temp.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LTW012/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LTW012/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": " Hue White Ambiance Candle E14",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"standby_usage": 0.3,
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly PM",
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository, standby_usage estimated"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LWA001/brightness.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LWA001/brightness.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LWA001/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LWA001/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue White Ambiance Bulb A60 E27",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly PM",
|
||||
"standby_usage": 0.4,
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository, estimated standby"
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "Hue White Filament Bulb A60 E27",
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly plug S",
|
||||
"measure_description": "Used script utils/measure_shelly.py from repository"
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LWA009/brightness.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LWA009/brightness.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LWA009/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LWA009/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue White 1600 A67 E27 1600lm",
|
||||
"standby_usage": 0.41,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "script",
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_description": "Measured twice with script made by bramstroker using 9 sec pauses and partially verified manual"
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.29,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
|
||||
"measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LWB006/brightness.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LWB006/brightness.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LWB006/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LWB006/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Hue White A60 E27 800lm",
|
||||
"standby_usage": 0.4,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "RRPM02 (Model); 4897037690801 (Barcode); Reduction Revolution Plug-in Power Meter",
|
||||
"measure_description": "Was not able to measure the standby usage, the value is assumed. Two bulbs were measured and the higher reading at each dim setting (0-100) was used"
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
"standby_usage": 0.39,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "Power Recorder; PR10-D (Model); Shenzen Zhurui Technology (Manufacturer)",
|
||||
"measure_description": "Bulbs was measured twice at each dim setting (100 points) and mean value was used when there were differences"
|
||||
}
|
||||
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LWO001/brightness.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LWO001/brightness.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LWO001/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LWO001/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"measure_device": "Shelly Plug S",
|
||||
"measure_method": "script",
|
||||
"measure_description": "Measured with utils/measure_v2, SAMPLE_COUNT=3",
|
||||
"name": "Hue White Filament Bulb G93 E27 w/ BT",
|
||||
"standby_usage": 0.35,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/signify/LWV001/brightness.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/signify/LWV001/brightness.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/signify/LWV001/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/signify/LWV001/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name":"Hue White Filament Bulb ST64 E27",
|
||||
"standby_usage":0.5,
|
||||
"supported_modes":[
|
||||
"lut"
|
||||
],
|
||||
"measure_device":"Gosund SP111 on Tasmota 9.5.0",
|
||||
"measure_description":"Measure Script for Tasmota",
|
||||
"measure_method":"script"
|
||||
}
|
||||
BIN
home-assistant/custom_components/powercalc/data/sonoff/B02BA60/color_temp.csv.gz
Executable file
BIN
home-assistant/custom_components/powercalc/data/sonoff/B02BA60/color_temp.csv.gz
Executable file
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/sonoff/B02BA60/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/sonoff/B02BA60/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"measure_device": "Sonoff DualR3",
|
||||
"measure_description": "Took several readings on every 10% of the lamp and did a linear regression to estimate the correct consumptions. Standby usage is assumed",
|
||||
"measure_method": "script",
|
||||
"name": "Sonoff B02 Light with dimmer and temperature",
|
||||
"standby_usage": 0.4,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
10
home-assistant/custom_components/powercalc/data/yeelight/YLDP01YL/model.json
Executable file
10
home-assistant/custom_components/powercalc/data/yeelight/YLDP01YL/model.json
Executable file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Yeelight LED 600lm 4000K 8W WiFi",
|
||||
"standby_usage": 1.1,
|
||||
"supported_modes": [
|
||||
"lut"
|
||||
],
|
||||
"measure_method": "manual",
|
||||
"measure_device": "RRPM02 (Model); 4897037690801 (Barcode); Reduction Revolution Plug-in Power Meter",
|
||||
"measure_description": "No comments"
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"after_dependencies": [
|
||||
"integration",
|
||||
"utility_meter"
|
||||
],
|
||||
"codeowners": [
|
||||
"@bramstroker"
|
||||
],
|
||||
@@ -7,7 +11,8 @@
|
||||
"fan",
|
||||
"switch",
|
||||
"binary_sensor",
|
||||
"light"
|
||||
"light",
|
||||
"template"
|
||||
],
|
||||
"documentation": "https://github.com/bramstroker/homeassistant-powercalc",
|
||||
"domain": "powercalc",
|
||||
@@ -15,5 +20,5 @@
|
||||
"issue_tracker": "https://github.com/bramstroker/homeassistant-powercalc/issues",
|
||||
"name": "Power consumption",
|
||||
"requirements": [],
|
||||
"version": "v0.2.7"
|
||||
"version": "v0.6.0"
|
||||
}
|
||||
86
home-assistant/custom_components/powercalc/model_discovery.py
Executable file
86
home-assistant/custom_components/powercalc/model_discovery.py
Executable file
@@ -0,0 +1,86 @@
|
||||
"""Utilities for auto discovery of light models."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.components.hue.const import DOMAIN as HUE_DOMAIN
|
||||
from homeassistant.components.light import Light
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import CONF_CUSTOM_MODEL_DIRECTORY, CONF_MANUFACTURER, CONF_MODEL
|
||||
from .light_model import LightModel
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def get_light_model(
|
||||
hass: HomeAssistantType, entity_entry, config: dict
|
||||
) -> Optional[LightModel]:
|
||||
manufacturer = config.get(CONF_MANUFACTURER)
|
||||
model = config.get(CONF_MODEL)
|
||||
if (manufacturer is None or model is None) and entity_entry:
|
||||
hue_model_info = await autodiscover_hue_model(hass, entity_entry)
|
||||
if hue_model_info:
|
||||
manufacturer = hue_model_info.manufacturer
|
||||
model = hue_model_info.model
|
||||
|
||||
if manufacturer is None or model is None:
|
||||
return None
|
||||
|
||||
custom_model_directory = config.get(CONF_CUSTOM_MODEL_DIRECTORY)
|
||||
if custom_model_directory:
|
||||
custom_model_directory = os.path.join(
|
||||
hass.config.config_dir, custom_model_directory
|
||||
)
|
||||
|
||||
return LightModel(manufacturer, model, custom_model_directory)
|
||||
|
||||
|
||||
async def autodiscover_hue_model(
|
||||
hass: HomeAssistantType, entity_entry
|
||||
) -> Optional[HueModelInfo]:
|
||||
# When Philips Hue model is enabled we can auto discover manufacturer and model from the bridge data
|
||||
if hass.data.get(HUE_DOMAIN) is None or entity_entry.platform != "hue":
|
||||
return
|
||||
|
||||
light = await find_hue_light(hass, entity_entry)
|
||||
if light is None:
|
||||
_LOGGER.error(
|
||||
"Cannot autodiscover model for '%s', not found in the hue bridge api",
|
||||
entity_entry.entity_id,
|
||||
)
|
||||
return
|
||||
|
||||
_LOGGER.debug(
|
||||
"Auto discovered Hue model for entity %s: (manufacturer=%s, model=%s)",
|
||||
entity_entry.entity_id,
|
||||
light.manufacturername,
|
||||
light.modelid,
|
||||
)
|
||||
|
||||
return HueModelInfo(light.manufacturername, light.modelid)
|
||||
|
||||
|
||||
async def find_hue_light(
|
||||
hass: HomeAssistantType, entity_entry: er.RegistryEntry
|
||||
) -> Light | None:
|
||||
"""Find the light in the Hue bridge, we need to extract the model id."""
|
||||
|
||||
bridge = hass.data[HUE_DOMAIN][entity_entry.config_entry_id]
|
||||
lights = bridge.api.lights
|
||||
for light_id in lights:
|
||||
light = bridge.api.lights[light_id]
|
||||
if light.uniqueid == entity_entry.unique_id:
|
||||
return light
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class HueModelInfo(NamedTuple):
|
||||
manufacturer: str
|
||||
model: str
|
||||
@@ -2,73 +2,96 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
import voluptuous as vol
|
||||
from homeassistant.components import (
|
||||
binary_sensor,
|
||||
climate,
|
||||
device_tracker,
|
||||
fan,
|
||||
input_boolean,
|
||||
input_select,
|
||||
light,
|
||||
media_player,
|
||||
remote,
|
||||
sensor,
|
||||
switch,
|
||||
vacuum,
|
||||
)
|
||||
from homeassistant.components.hue.const import DOMAIN as HUE_DOMAIN
|
||||
from homeassistant.components.light import PLATFORM_SCHEMA, Light
|
||||
from homeassistant.components.integration.sensor import (
|
||||
TRAPEZOIDAL_METHOD,
|
||||
IntegrationSensor,
|
||||
)
|
||||
from homeassistant.components.light import PLATFORM_SCHEMA
|
||||
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
|
||||
from homeassistant.components.utility_meter import DEFAULT_OFFSET
|
||||
from homeassistant.components.utility_meter.sensor import UtilityMeterSensor
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_ID,
|
||||
CONF_NAME,
|
||||
CONF_SCAN_INTERVAL,
|
||||
DEVICE_CLASS_POWER,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
POWER_WATT,
|
||||
STATE_NOT_HOME,
|
||||
STATE_OFF,
|
||||
STATE_STANDBY,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
TIME_HOURS,
|
||||
)
|
||||
from homeassistant.core import callback, split_entity_id
|
||||
from homeassistant.helpers.entity import Entity, async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_state_change_event,
|
||||
async_track_time_interval,
|
||||
)
|
||||
from homeassistant.helpers.typing import (
|
||||
ConfigType,
|
||||
DiscoveryInfoType,
|
||||
HomeAssistantType,
|
||||
)
|
||||
from homeassistant.core import split_entity_id
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .common import SourceEntity
|
||||
from .const import (
|
||||
CALCULATION_MODES,
|
||||
CONF_CREATE_ENERGY_SENSOR,
|
||||
CONF_CREATE_ENERGY_SENSORS,
|
||||
CONF_CREATE_UTILITY_METERS,
|
||||
CONF_CUSTOM_MODEL_DIRECTORY,
|
||||
CONF_DISABLE_STANDBY_USAGE,
|
||||
CONF_ENERGY_SENSOR_NAMING,
|
||||
CONF_FIXED,
|
||||
CONF_LINEAR,
|
||||
CONF_MANUFACTURER,
|
||||
CONF_MAX_WATT,
|
||||
CONF_MIN_WATT,
|
||||
CONF_MODE,
|
||||
CONF_MODEL,
|
||||
CONF_MULTIPLY_FACTOR,
|
||||
CONF_MULTIPLY_FACTOR_STANDBY,
|
||||
CONF_POWER_SENSOR_NAMING,
|
||||
CONF_STANDBY_USAGE,
|
||||
CONF_WATT,
|
||||
CONF_UTILITY_METER_TYPES,
|
||||
DATA_CALCULATOR_FACTORY,
|
||||
DOMAIN,
|
||||
DOMAIN_CONFIG,
|
||||
MODE_FIXED,
|
||||
MODE_LINEAR,
|
||||
MODE_LUT,
|
||||
)
|
||||
from .errors import ModelNotSupported, StrategyConfigurationError, UnsupportedMode
|
||||
from .light_model import LightModel
|
||||
from .model_discovery import get_light_model
|
||||
from .strategy_fixed import CONFIG_SCHEMA as FIXED_SCHEMA
|
||||
from .strategy_interface import PowerCalculationStrategyInterface
|
||||
from .strategy_linear import CONFIG_SCHEMA as LINEAR_SCHEMA
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
SCAN_INTERVAL = timedelta(minutes=30)
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
cv.deprecated(CONF_MIN_WATT),
|
||||
cv.deprecated(CONF_MAX_WATT),
|
||||
cv.deprecated(CONF_WATT),
|
||||
PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
@@ -78,106 +101,223 @@ PLATFORM_SCHEMA = vol.All(
|
||||
switch.DOMAIN,
|
||||
fan.DOMAIN,
|
||||
binary_sensor.DOMAIN,
|
||||
climate.DOMAIN,
|
||||
device_tracker.DOMAIN,
|
||||
remote.DOMAIN,
|
||||
media_player.DOMAIN,
|
||||
input_boolean.DOMAIN,
|
||||
input_select.DOMAIN,
|
||||
sensor.DOMAIN,
|
||||
vacuum.DOMAIN,
|
||||
)
|
||||
),
|
||||
vol.Optional(CONF_MODEL): cv.string,
|
||||
vol.Optional(CONF_MANUFACTURER): cv.string,
|
||||
vol.Optional(CONF_MODE): vol.In([MODE_LUT, MODE_FIXED, MODE_LINEAR]),
|
||||
vol.Optional(CONF_MIN_WATT): cv.string,
|
||||
vol.Optional(CONF_MAX_WATT): cv.string,
|
||||
vol.Optional(CONF_WATT): cv.string,
|
||||
vol.Optional(CONF_MODE): vol.In(CALCULATION_MODES),
|
||||
vol.Optional(CONF_STANDBY_USAGE): vol.Coerce(float),
|
||||
vol.Optional(CONF_DISABLE_STANDBY_USAGE, default=False): cv.boolean,
|
||||
vol.Optional(CONF_CUSTOM_MODEL_DIRECTORY): cv.string,
|
||||
vol.Optional(CONF_FIXED): FIXED_SCHEMA,
|
||||
vol.Optional(CONF_LINEAR): LINEAR_SCHEMA,
|
||||
vol.Optional(CONF_CREATE_ENERGY_SENSOR): cv.boolean,
|
||||
vol.Optional(CONF_MULTIPLY_FACTOR): vol.Coerce(float),
|
||||
vol.Optional(CONF_MULTIPLY_FACTOR_STANDBY, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
NAME_FORMAT = "{} power"
|
||||
ENERGY_ICON = "mdi:lightning-bolt"
|
||||
ATTR_SOURCE_ENTITY = "source_entity"
|
||||
ATTR_SOURCE_DOMAIN = "source_domain"
|
||||
OFF_STATES = [STATE_OFF, STATE_NOT_HOME, STATE_STANDBY]
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistantType, config, async_add_entities, discovery_info=None
|
||||
hass: HomeAssistantType,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
):
|
||||
"""Set up the sensor platform."""
|
||||
|
||||
calculation_strategy_factory = hass.data[DOMAIN][DATA_CALCULATOR_FACTORY]
|
||||
component_config = hass.data[DOMAIN][DOMAIN_CONFIG]
|
||||
|
||||
entity_id = config[CONF_ENTITY_ID]
|
||||
source_entity = config[CONF_ENTITY_ID]
|
||||
source_entity_domain, source_object_id = split_entity_id(source_entity)
|
||||
|
||||
entity_registry = await er.async_get_registry(hass)
|
||||
entity_entry = entity_registry.async_get(entity_id)
|
||||
entity_state = hass.states.get(entity_id)
|
||||
entity_entry = entity_registry.async_get(source_entity)
|
||||
|
||||
unique_id = None
|
||||
|
||||
if entity_entry:
|
||||
entity_name = entity_entry.name or entity_entry.original_name
|
||||
entity_domain = entity_entry.domain
|
||||
source_entity_name = entity_entry.name or entity_entry.original_name
|
||||
source_entity_domain = entity_entry.domain
|
||||
unique_id = entity_entry.unique_id
|
||||
elif entity_state:
|
||||
entity_name = entity_state.name
|
||||
entity_domain = entity_state.domain
|
||||
else:
|
||||
entity_name = split_entity_id(entity_id)[1].replace("_", " ")
|
||||
entity_domain = split_entity_id(entity_id)[0]
|
||||
source_entity_name = source_object_id.replace("_", " ")
|
||||
|
||||
name = config.get(CONF_NAME) or NAME_FORMAT.format(entity_name)
|
||||
entity_state = hass.states.get(source_entity)
|
||||
if entity_state:
|
||||
source_entity_name = entity_state.name
|
||||
|
||||
capabilities = entity_entry.capabilities if entity_entry else []
|
||||
source_entity = SourceEntity(
|
||||
unique_id,
|
||||
source_object_id,
|
||||
source_entity,
|
||||
source_entity_name,
|
||||
source_entity_domain,
|
||||
capabilities,
|
||||
)
|
||||
|
||||
try:
|
||||
power_sensor = await create_power_sensor(
|
||||
hass, entity_entry, config, component_config, source_entity
|
||||
)
|
||||
except (ModelNotSupported, StrategyConfigurationError) as err:
|
||||
pass
|
||||
|
||||
entities_to_add = [power_sensor]
|
||||
|
||||
should_create_energy_sensor = component_config.get(CONF_CREATE_ENERGY_SENSORS)
|
||||
if CONF_CREATE_ENERGY_SENSOR in config:
|
||||
should_create_energy_sensor = config.get(CONF_CREATE_ENERGY_SENSOR)
|
||||
|
||||
if should_create_energy_sensor:
|
||||
energy_sensor = await create_energy_sensor(
|
||||
hass, component_config, config, power_sensor, source_entity
|
||||
)
|
||||
entities_to_add.append(energy_sensor)
|
||||
|
||||
if component_config.get(CONF_CREATE_UTILITY_METERS):
|
||||
meter_types = component_config.get(CONF_UTILITY_METER_TYPES)
|
||||
for meter_type in meter_types:
|
||||
entities_to_add.append(
|
||||
create_utility_meter_sensor(energy_sensor, meter_type)
|
||||
)
|
||||
|
||||
async_add_entities(entities_to_add)
|
||||
|
||||
|
||||
async def create_power_sensor(
|
||||
hass: HomeAssistantType,
|
||||
entity_entry,
|
||||
sensor_config: dict,
|
||||
component_config: dict,
|
||||
source_entity: SourceEntity,
|
||||
) -> VirtualPowerSensor:
|
||||
"""Create the power sensor entity"""
|
||||
|
||||
calculation_strategy_factory = hass.data[DOMAIN][DATA_CALCULATOR_FACTORY]
|
||||
|
||||
name_pattern = component_config.get(CONF_POWER_SENSOR_NAMING)
|
||||
name = sensor_config.get(CONF_NAME) or source_entity.name
|
||||
name = name_pattern.format(name)
|
||||
object_id = sensor_config.get(CONF_NAME) or source_entity.object_id
|
||||
entity_id = async_generate_entity_id("sensor.{}", name_pattern.format(object_id), hass=hass)
|
||||
|
||||
light_model = None
|
||||
try:
|
||||
light_model = await get_light_model(hass, entity_entry, config)
|
||||
except (ModelNotSupported) as err:
|
||||
_LOGGER.info("Model not found in library %s: %s", entity_id, err)
|
||||
light_model = await get_light_model(hass, entity_entry, sensor_config)
|
||||
except ModelNotSupported as err:
|
||||
mode = select_calculation_mode(sensor_config, None)
|
||||
if mode == MODE_LUT:
|
||||
_LOGGER.error(
|
||||
"Model not found in library %s: %s", source_entity.entity_id, err
|
||||
)
|
||||
raise err
|
||||
|
||||
try:
|
||||
mode = select_calculation_mode(config, light_model)
|
||||
mode = select_calculation_mode(sensor_config, light_model)
|
||||
calculation_strategy = calculation_strategy_factory.create(
|
||||
config, mode, light_model, entity_domain
|
||||
sensor_config, mode, light_model, source_entity.domain
|
||||
)
|
||||
await calculation_strategy.validate_config(entity_entry)
|
||||
await calculation_strategy.validate_config(source_entity)
|
||||
except (ModelNotSupported, UnsupportedMode) as err:
|
||||
_LOGGER.error("Skipping sensor setup %s: %s", entity_id, err)
|
||||
return
|
||||
_LOGGER.error("Skipping sensor setup %s: %s", source_entity.entity_id, err)
|
||||
raise err
|
||||
except StrategyConfigurationError as err:
|
||||
_LOGGER.error("Error setting up calculation strategy: %s", err)
|
||||
return
|
||||
_LOGGER.error(
|
||||
"Error setting up calculation strategy for %s: %s",
|
||||
source_entity.entity_id,
|
||||
err,
|
||||
)
|
||||
raise err
|
||||
|
||||
standby_usage = None
|
||||
if config.get(CONF_DISABLE_STANDBY_USAGE) == False:
|
||||
standby_usage = config.get(CONF_STANDBY_USAGE)
|
||||
if not sensor_config.get(CONF_DISABLE_STANDBY_USAGE):
|
||||
standby_usage = sensor_config.get(CONF_STANDBY_USAGE)
|
||||
if standby_usage is None and light_model is not None:
|
||||
standby_usage = light_model.standby_usage
|
||||
|
||||
_LOGGER.debug(
|
||||
"Setting up power sensor. entity_id:%s sensor_name:%s strategy=%s manufacturer=%s model=%s standby_usage=%s",
|
||||
entity_id,
|
||||
"Setting up power sensor. entity_id:%s sensor_name:%s strategy=%s manufacturer=%s model=%s standby_usage=%s unique_id=%s",
|
||||
source_entity.entity_id,
|
||||
name,
|
||||
calculation_strategy.__class__.__name__,
|
||||
light_model.manufacturer if light_model else "",
|
||||
light_model.model if light_model else "",
|
||||
standby_usage,
|
||||
source_entity.unique_id,
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
VirtualPowerSensor(
|
||||
power_calculator=calculation_strategy,
|
||||
name=name,
|
||||
entity_id=entity_id,
|
||||
unique_id=unique_id,
|
||||
standby_usage=standby_usage,
|
||||
)
|
||||
]
|
||||
return VirtualPowerSensor(
|
||||
power_calculator=calculation_strategy,
|
||||
entity_id=entity_id,
|
||||
name=name,
|
||||
source_entity=source_entity.entity_id,
|
||||
source_domain=source_entity.domain,
|
||||
unique_id=source_entity.unique_id,
|
||||
standby_usage=standby_usage,
|
||||
scan_interval=component_config.get(CONF_SCAN_INTERVAL),
|
||||
multiply_factor=sensor_config.get(CONF_MULTIPLY_FACTOR),
|
||||
multiply_factor_standby=sensor_config.get(CONF_MULTIPLY_FACTOR_STANDBY),
|
||||
)
|
||||
|
||||
|
||||
def select_calculation_mode(config: dict, light_model: LightModel):
|
||||
async def create_energy_sensor(
|
||||
hass: HomeAssistantType,
|
||||
component_config: dict,
|
||||
sensor_config: dict,
|
||||
power_sensor: VirtualPowerSensor,
|
||||
source_entity: SourceEntity,
|
||||
) -> VirtualEnergySensor:
|
||||
name_pattern = component_config.get(CONF_ENERGY_SENSOR_NAMING)
|
||||
name = sensor_config.get(CONF_NAME) or source_entity.name
|
||||
name = name_pattern.format(name)
|
||||
object_id = sensor_config.get(CONF_NAME) or source_entity.object_id
|
||||
entity_id = async_generate_entity_id(
|
||||
"sensor.{}", name_pattern.format(object_id), hass=hass
|
||||
)
|
||||
|
||||
_LOGGER.debug("Creating energy sensor: %s", name)
|
||||
return VirtualEnergySensor(
|
||||
source_entity=power_sensor.entity_id,
|
||||
unique_id=source_entity.unique_id,
|
||||
entity_id=entity_id,
|
||||
name=name,
|
||||
round_digits=4,
|
||||
unit_prefix="k",
|
||||
unit_of_measurement=None,
|
||||
unit_time=TIME_HOURS,
|
||||
integration_method=TRAPEZOIDAL_METHOD,
|
||||
powercalc_source_entity=source_entity.entity_id,
|
||||
powercalc_source_domain=source_entity.domain,
|
||||
)
|
||||
|
||||
|
||||
def create_utility_meter_sensor(
|
||||
energy_sensor: VirtualEnergySensor, meter_type: str
|
||||
) -> VirtualUtilityMeterSensor:
|
||||
name = f"{energy_sensor.name} {meter_type}"
|
||||
entity_id = f"{energy_sensor.entity_id}_{meter_type}"
|
||||
_LOGGER.debug("Creating utility_meter sensor: %s", name)
|
||||
return VirtualUtilityMeterSensor(
|
||||
energy_sensor.entity_id, name, meter_type, entity_id
|
||||
)
|
||||
|
||||
|
||||
def select_calculation_mode(config: dict, light_model: LightModel) -> str:
|
||||
"""Select the calculation mode"""
|
||||
config_mode = config.get(CONF_MODE)
|
||||
if config_mode:
|
||||
@@ -192,78 +332,11 @@ def select_calculation_mode(config: dict, light_model: LightModel):
|
||||
if light_model:
|
||||
return light_model.supported_modes[0]
|
||||
|
||||
# BC compat
|
||||
if config.get(CONF_MIN_WATT):
|
||||
return MODE_LINEAR
|
||||
|
||||
# BC compat
|
||||
if config.get(CONF_WATT):
|
||||
return MODE_FIXED
|
||||
|
||||
raise UnsupportedMode(
|
||||
"Cannot select a mode (LINEAR, FIXED or LUT), supply it in the config"
|
||||
)
|
||||
|
||||
|
||||
async def get_light_model(hass, entity_entry, config: dict) -> Optional[LightModel]:
|
||||
manufacturer = config.get(CONF_MANUFACTURER)
|
||||
model = config.get(CONF_MODEL)
|
||||
if (manufacturer is None or model is None) and entity_entry:
|
||||
hue_model_data = await autodiscover_hue_model(hass, entity_entry)
|
||||
if hue_model_data:
|
||||
manufacturer = hue_model_data["manufacturer"]
|
||||
model = hue_model_data["model"]
|
||||
|
||||
if manufacturer is None or model is None:
|
||||
return None
|
||||
|
||||
custom_model_directory = config.get(CONF_CUSTOM_MODEL_DIRECTORY)
|
||||
if custom_model_directory:
|
||||
custom_model_directory = os.path.join(
|
||||
hass.config.config_dir, custom_model_directory
|
||||
)
|
||||
|
||||
return LightModel(manufacturer, model, custom_model_directory)
|
||||
|
||||
|
||||
async def autodiscover_hue_model(hass, entity_entry):
|
||||
# When Philips Hue model is enabled we can auto discover manufacturer and model from the bridge data
|
||||
if hass.data.get(HUE_DOMAIN) == None or entity_entry.platform != "hue":
|
||||
return
|
||||
|
||||
light = await find_hue_light(hass, entity_entry)
|
||||
if light is None:
|
||||
_LOGGER.error(
|
||||
"Cannot autodisover model for '%s', not found in the hue bridge api",
|
||||
entity_entry.entity_id,
|
||||
)
|
||||
return
|
||||
|
||||
_LOGGER.debug(
|
||||
"Auto discovered Hue model for entity %s: (manufacturer=%s, model=%s)",
|
||||
entity_entry.entity_id,
|
||||
light.manufacturername,
|
||||
light.modelid,
|
||||
)
|
||||
|
||||
return {"manufacturer": light.manufacturername, "model": light.modelid}
|
||||
|
||||
|
||||
async def find_hue_light(
|
||||
hass: HomeAssistantType, entity_entry: er.RegistryEntry
|
||||
) -> Light | None:
|
||||
"""Find the light in the Hue bridge, we need to extract the model id."""
|
||||
|
||||
bridge = hass.data[HUE_DOMAIN][entity_entry.config_entry_id]
|
||||
lights = bridge.api.lights
|
||||
for light_id in lights:
|
||||
light = bridge.api.lights[light_id]
|
||||
if light.uniqueid == entity_entry.unique_id:
|
||||
return light
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class VirtualPowerSensor(Entity):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
@@ -274,19 +347,29 @@ class VirtualPowerSensor(Entity):
|
||||
def __init__(
|
||||
self,
|
||||
power_calculator: PowerCalculationStrategyInterface,
|
||||
name: str,
|
||||
entity_id: str,
|
||||
name: str,
|
||||
source_entity: str,
|
||||
source_domain: str,
|
||||
unique_id: str,
|
||||
standby_usage: float | None,
|
||||
scan_interval,
|
||||
multiply_factor: float | None,
|
||||
multiply_factor_standby: bool,
|
||||
):
|
||||
"""Initialize the sensor."""
|
||||
self._power_calculator = power_calculator
|
||||
self._entity_id = entity_id
|
||||
self._source_entity = source_entity
|
||||
self._source_domain = source_domain
|
||||
self._name = name
|
||||
self._power = None
|
||||
self._unique_id = unique_id
|
||||
self._standby_usage = standby_usage
|
||||
self._attr_force_update = True
|
||||
self._attr_unique_id = unique_id
|
||||
self._scan_interval = scan_interval
|
||||
self._multiply_factor = multiply_factor
|
||||
self._multiply_factor_standby = multiply_factor_standby
|
||||
self.entity_id = entity_id
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
@@ -301,29 +384,49 @@ class VirtualPowerSensor(Entity):
|
||||
"""Add listeners and get initial state."""
|
||||
|
||||
async_track_state_change_event(
|
||||
self.hass, [self._entity_id], appliance_state_listener
|
||||
self.hass, [self._source_entity], appliance_state_listener
|
||||
)
|
||||
|
||||
new_state = self.hass.states.get(self._entity_id)
|
||||
new_state = self.hass.states.get(self._source_entity)
|
||||
|
||||
await self._update_power_sensor(new_state)
|
||||
|
||||
@callback
|
||||
def async_update(event_time=None):
|
||||
"""Update the entity."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async_track_time_interval(self.hass, async_update, self._scan_interval)
|
||||
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, home_assistant_startup
|
||||
)
|
||||
|
||||
async def _update_power_sensor(self, state) -> bool:
|
||||
"""Update power sensor based on new dependant hue light state."""
|
||||
if state is None or state.state == STATE_UNKNOWN:
|
||||
if (
|
||||
state is None
|
||||
or state.state == STATE_UNKNOWN
|
||||
or state.state == STATE_UNAVAILABLE
|
||||
):
|
||||
self._power = None
|
||||
self.async_write_ha_state()
|
||||
return False
|
||||
|
||||
if state.state == STATE_UNAVAILABLE:
|
||||
return False
|
||||
|
||||
if state.state == STATE_OFF or state.state == STATE_STANDBY:
|
||||
if state.state in OFF_STATES:
|
||||
self._power = self._standby_usage or 0
|
||||
if self._multiply_factor and self._multiply_factor_standby:
|
||||
self._power *= self._multiply_factor
|
||||
else:
|
||||
self._power = await self._power_calculator.calculate(state)
|
||||
if self._multiply_factor and self._power is not None:
|
||||
self._power *= self._multiply_factor
|
||||
|
||||
if self._power is None:
|
||||
self.async_write_ha_state()
|
||||
return False
|
||||
|
||||
self._power = round(self._power, 2)
|
||||
|
||||
_LOGGER.debug(
|
||||
'State changed to "%s" for entity "%s". Power:%s',
|
||||
@@ -335,6 +438,14 @@ class VirtualPowerSensor(Entity):
|
||||
self.async_write_ha_state()
|
||||
return True
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return entity state attributes."""
|
||||
return {
|
||||
ATTR_SOURCE_ENTITY: self._source_entity,
|
||||
ATTR_SOURCE_DOMAIN: self._source_domain,
|
||||
}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
@@ -345,12 +456,56 @@ class VirtualPowerSensor(Entity):
|
||||
"""Return the state of the sensor."""
|
||||
return self._power
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self._power is not None
|
||||
|
||||
|
||||
class VirtualEnergySensor(IntegrationSensor):
|
||||
def __init__(
|
||||
self,
|
||||
source_entity,
|
||||
unique_id,
|
||||
entity_id,
|
||||
name,
|
||||
round_digits,
|
||||
unit_prefix,
|
||||
unit_time,
|
||||
unit_of_measurement,
|
||||
integration_method,
|
||||
powercalc_source_entity: str,
|
||||
powercalc_source_domain: str,
|
||||
):
|
||||
super().__init__(
|
||||
source_entity,
|
||||
name,
|
||||
round_digits,
|
||||
unit_prefix,
|
||||
unit_time,
|
||||
unit_of_measurement,
|
||||
integration_method,
|
||||
)
|
||||
self._powercalc_source_entity = powercalc_source_entity
|
||||
self._powercalc_source_domain = powercalc_source_domain
|
||||
self.entity_id = entity_id
|
||||
if unique_id:
|
||||
self._attr_unique_id = f"{unique_id}_energy"
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of the acceleration sensor."""
|
||||
state_attr = super().extra_state_attributes
|
||||
state_attr[ATTR_SOURCE_ENTITY] = self._powercalc_source_entity
|
||||
state_attr[ATTR_SOURCE_DOMAIN] = self._powercalc_source_domain
|
||||
return state_attr
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return ENERGY_ICON
|
||||
|
||||
|
||||
class VirtualUtilityMeterSensor(UtilityMeterSensor):
|
||||
def __init__(self, source_entity, name, meter_type, entity_id):
|
||||
super().__init__(source_entity, name, meter_type, DEFAULT_OFFSET, False)
|
||||
self.entity_id = entity_id
|
||||
|
||||
@@ -3,22 +3,27 @@ from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
import voluptuous as vol
|
||||
from homeassistant.components import climate, media_player, vacuum
|
||||
from homeassistant.core import State
|
||||
|
||||
from .common import SourceEntity
|
||||
from .const import CONF_POWER, CONF_STATES_POWER
|
||||
from .errors import StrategyConfigurationError
|
||||
from .strategy_interface import PowerCalculationStrategyInterface
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_POWER): vol.Coerce(float),
|
||||
vol.Optional(CONF_STATES_POWER, default={}): vol.Schema(
|
||||
{cv.string: vol.Coerce(float)}
|
||||
),
|
||||
vol.Optional(CONF_STATES_POWER): vol.Schema({cv.string: vol.Coerce(float)}),
|
||||
}
|
||||
)
|
||||
|
||||
STATE_BASED_ENTITY_DOMAINS = [
|
||||
climate.DOMAIN,
|
||||
vacuum.DOMAIN,
|
||||
]
|
||||
|
||||
|
||||
class FixedStrategy(PowerCalculationStrategyInterface):
|
||||
def __init__(
|
||||
@@ -28,11 +33,27 @@ class FixedStrategy(PowerCalculationStrategyInterface):
|
||||
self._per_state_power = per_state_power
|
||||
|
||||
async def calculate(self, entity_state: State) -> Optional[int]:
|
||||
if entity_state.state in self._per_state_power:
|
||||
return self._per_state_power.get(entity_state.state)
|
||||
if self._per_state_power is not None:
|
||||
# Lookup by state
|
||||
if entity_state.state in self._per_state_power:
|
||||
return self._per_state_power.get(entity_state.state)
|
||||
else:
|
||||
# Lookup by state attribute (attribute|value)
|
||||
for state_key, power in self._per_state_power.items():
|
||||
if "|" in state_key:
|
||||
attribute, value = state_key.split("|", 2)
|
||||
if entity_state.attributes.get(attribute) == value:
|
||||
return power
|
||||
|
||||
return self._power
|
||||
|
||||
async def validate_config(self, entity_entry: er.RegistryEntry):
|
||||
async def validate_config(self, source_entity: SourceEntity):
|
||||
"""Validate correct setup of the strategy"""
|
||||
pass
|
||||
|
||||
if (
|
||||
source_entity.domain in STATE_BASED_ENTITY_DOMAINS
|
||||
and self._per_state_power is None
|
||||
):
|
||||
raise StrategyConfigurationError(
|
||||
"This entity can only work with 'state_power' not 'power'"
|
||||
)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
from typing import Optional
|
||||
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.core import State
|
||||
|
||||
from .common import SourceEntity
|
||||
|
||||
|
||||
class PowerCalculationStrategyInterface:
|
||||
async def calculate(self, entity_state: State) -> Optional[int]:
|
||||
"""Calculate power consumption based on entity state"""
|
||||
pass
|
||||
|
||||
async def validate_config(self, entity_entry: er.RegistryEntry):
|
||||
async def validate_config(self, source_entity: SourceEntity):
|
||||
"""Validate correct setup of the strategy"""
|
||||
pass
|
||||
|
||||
@@ -4,7 +4,6 @@ import logging
|
||||
from typing import Optional
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
import voluptuous as vol
|
||||
from homeassistant.components import fan, light
|
||||
from homeassistant.components.fan import ATTR_PERCENTAGE
|
||||
@@ -12,10 +11,12 @@ from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||
from homeassistant.core import State
|
||||
from homeassistant.helpers.config_validation import entity_domain
|
||||
|
||||
from .common import SourceEntity
|
||||
from .const import CONF_CALIBRATE, CONF_MAX_POWER, CONF_MIN_POWER
|
||||
from .errors import StrategyConfigurationError
|
||||
from .strategy_interface import PowerCalculationStrategyInterface
|
||||
|
||||
ALLOWED_DOMAINS = [fan.DOMAIN, light.DOMAIN]
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_CALIBRATE): vol.All(
|
||||
@@ -40,6 +41,9 @@ class LinearStrategy(PowerCalculationStrategyInterface):
|
||||
|
||||
if entity_state.domain == light.DOMAIN:
|
||||
value = attrs.get(ATTR_BRIGHTNESS)
|
||||
# Some integrations set a higher brightness value than 255, causing powercalc to misbehave
|
||||
if value > 255:
|
||||
value = 255
|
||||
if value is None:
|
||||
_LOGGER.error("No brightness for entity: %s", entity_state.entity_id)
|
||||
return None
|
||||
@@ -92,9 +96,16 @@ class LinearStrategy(PowerCalculationStrategyInterface):
|
||||
sorted_list = sorted(list, key=lambda tup: tup[0])
|
||||
return sorted_list
|
||||
|
||||
async def validate_config(self, entity_entry: er.RegistryEntry):
|
||||
async def validate_config(self, source_entity: SourceEntity):
|
||||
"""Validate correct setup of the strategy"""
|
||||
|
||||
if source_entity.domain not in ALLOWED_DOMAINS:
|
||||
raise StrategyConfigurationError(
|
||||
"Entity not supported for linear mode. Must be one of: {}".format(
|
||||
",".join(ALLOWED_DOMAINS)
|
||||
)
|
||||
)
|
||||
|
||||
if self._config.get(CONF_CALIBRATE) is None:
|
||||
if self._config.get(CONF_MIN_POWER) is None:
|
||||
raise StrategyConfigurationError("You must supply min power")
|
||||
|
||||
@@ -8,7 +8,6 @@ from csv import reader
|
||||
from functools import partial
|
||||
from typing import Optional
|
||||
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.components import light
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
@@ -22,6 +21,7 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.core import State
|
||||
|
||||
from .common import SourceEntity
|
||||
from .errors import (
|
||||
LutFileNotFound,
|
||||
ModelNotSupported,
|
||||
@@ -45,7 +45,7 @@ class LutRegistry:
|
||||
) -> dict | None:
|
||||
cache_key = f"{light_model.manufacturer}_{light_model.model}_{color_mode}"
|
||||
lookup_dict = self._lookup_dictionaries.get(cache_key)
|
||||
if lookup_dict == None:
|
||||
if lookup_dict is None:
|
||||
defaultdict_of_dict = partial(defaultdict, dict)
|
||||
lookup_dict = defaultdict(defaultdict_of_dict)
|
||||
|
||||
@@ -101,6 +101,8 @@ class LutStrategy(PowerCalculationStrategyInterface):
|
||||
if brightness is None:
|
||||
_LOGGER.error("No brightness for entity: %s", entity_state.entity_id)
|
||||
return None
|
||||
if brightness > 255:
|
||||
brightness = 255
|
||||
|
||||
try:
|
||||
lookup_table = await self._lut_registry.get_lookup_dictionary(
|
||||
@@ -141,23 +143,26 @@ class LutStrategy(PowerCalculationStrategyInterface):
|
||||
or dict[min(dict.keys(), key=lambda key: abs(key - search_key))]
|
||||
)
|
||||
|
||||
async def validate_config(
|
||||
self,
|
||||
entity_entry: er.RegistryEntry,
|
||||
):
|
||||
if entity_entry.domain != light.DOMAIN:
|
||||
def get_nearest_lower(self, dict: dict, search_key):
|
||||
return (
|
||||
dict.get(search_key)
|
||||
or dict[min(dict.keys(), key=lambda key: abs(key - search_key))]
|
||||
)
|
||||
|
||||
async def validate_config(self, source_entity: SourceEntity):
|
||||
if source_entity.domain != light.DOMAIN:
|
||||
raise StrategyConfigurationError("Only light entities can use the LUT mode")
|
||||
|
||||
if self._model.manufacturer is None:
|
||||
_LOGGER.error(
|
||||
"Manufacturer not supplied for entity: %s", entity_entry.entity_id
|
||||
"Manufacturer not supplied for entity: %s", source_entity.entity_id
|
||||
)
|
||||
|
||||
if self._model.model is None:
|
||||
_LOGGER.error("Model not supplied for entity: %s", entity_entry.entity_id)
|
||||
_LOGGER.error("Model not supplied for entity: %s", source_entity.entity_id)
|
||||
return
|
||||
|
||||
supported_color_modes = entity_entry.capabilities[
|
||||
supported_color_modes = source_entity.capabilities[
|
||||
light.ATTR_SUPPORTED_COLOR_MODES
|
||||
]
|
||||
for color_mode in supported_color_modes:
|
||||
|
||||
@@ -22,20 +22,11 @@ sensor:
|
||||
# Custom Integration Powercalc
|
||||
## Power consumption lights
|
||||
- platform: powercalc
|
||||
name: power_light_bedroom_ceiling_1
|
||||
entity_id: light.bedroom_ceiling_1
|
||||
manufacturer: signify
|
||||
model: LCT010
|
||||
- platform: powercalc
|
||||
name: power_light_bedroom_ceiling_2
|
||||
entity_id: light.bedroom_ceiling_2
|
||||
manufacturer: signify
|
||||
model: LCT010
|
||||
- platform: powercalc
|
||||
name: power_light_bedroom_ceiling_3
|
||||
entity_id: light.bedroom_ceiling_3
|
||||
name: power_light_bedroom_ceiling
|
||||
entity_id: light.bedroom_ceiling
|
||||
manufacturer: signify
|
||||
model: LCT010
|
||||
multiply_factor: 3
|
||||
- platform: powercalc
|
||||
name: power_light_bedroom_bed
|
||||
entity_id: light.bedroom_bed
|
||||
|
||||
@@ -15,20 +15,11 @@ sensor:
|
||||
# Custom Integration Powercalc
|
||||
## Power consumption lights
|
||||
- platform: powercalc
|
||||
name: power_light_dressroom_ceiling_1
|
||||
entity_id: light.dressroom_ceiling_1
|
||||
manufacturer: signify
|
||||
model: LCT010
|
||||
- platform: powercalc
|
||||
name: power_light_dressroom_ceiling_2
|
||||
entity_id: light.dressroom_ceiling_2
|
||||
manufacturer: signify
|
||||
model: LCT010
|
||||
- platform: powercalc
|
||||
name: power_light_dressroom_ceiling_3
|
||||
entity_id: light.dressroom_ceiling_3
|
||||
name: power_light_dressroom
|
||||
entity_id: light.dressroom
|
||||
manufacturer: signify
|
||||
model: LCT010
|
||||
multiply_factor: 3
|
||||
|
||||
automation:
|
||||
# Turn lights on (if not already on) when motion is detected
|
||||
|
||||
@@ -14,6 +14,10 @@ input_number:
|
||||
max: 2
|
||||
step: 0.01
|
||||
|
||||
# Powercalc General Settings
|
||||
powercalc:
|
||||
create_energy_sensors: false
|
||||
|
||||
sensor:
|
||||
# Convert power (W) to energy (kWh)
|
||||
- platform: integration
|
||||
@@ -29,10 +33,10 @@ template:
|
||||
device_class: power
|
||||
unit_of_measurement: W
|
||||
state: >
|
||||
{% set office = states('sensor.power_light_office_ceiling_1')|float + states('sensor.power_light_office_ceiling_2')|float + states('sensor.power_light_office_ceiling_3')|float %}
|
||||
{% set bedroom_ceiling = states('sensor.power_light_bedroom_ceiling_1')|float + states('sensor.power_light_bedroom_ceiling_2')|float + states('sensor.power_light_bedroom_ceiling_3')|float %}
|
||||
{% set office = states('sensor.power_light_office')|float %}
|
||||
{% set bedroom_ceiling = states('sensor.power_light_bedroom_ceiling')|float %}
|
||||
{% set bedroom_bed = states('sensor.power_light_bedroom_bed')|float %}
|
||||
{% set dressroom = states('sensor.power_light_dressroom_ceiling_1')|float + states('sensor.power_light_dressroom_ceiling_2')|float + states('sensor.power_light_dressroom_ceiling_3')|float %}
|
||||
{% set dressroom = states('sensor.power_light_dressroom')|float %}
|
||||
{% set livingroom = states('sensor.power_light_livingroom_floor_front')|float + states('sensor.power_light_livingroom_back')|float %}
|
||||
{{ office + bedroom_ceiling + bedroom_bed + dressroom + livingroom }}
|
||||
# Current Electricity Tariff
|
||||
|
||||
@@ -52,10 +52,9 @@ sensor:
|
||||
- platform: powercalc
|
||||
name: power_receiver_livingroom
|
||||
entity_id: media_player.receiver_livingroom
|
||||
standby_usage: 2.7
|
||||
fixed:
|
||||
states_power:
|
||||
on: 500
|
||||
off: 2.7
|
||||
power: 500
|
||||
# Convert power (W) to energy (kWh) for beamer
|
||||
- platform: integration
|
||||
source: sensor.power_beamer
|
||||
|
||||
@@ -32,26 +32,13 @@ sensor:
|
||||
# Custom Integration Powercalc
|
||||
## Power consumption lights
|
||||
- platform: powercalc
|
||||
name: power_light_office_ceiling_1
|
||||
entity_id: light.office_ceiling_1
|
||||
name: power_light_office
|
||||
entity_id: light.office
|
||||
multiply_factor: 3
|
||||
linear:
|
||||
min_power: 0.5
|
||||
max_power: 5
|
||||
standby_usage: 0.2
|
||||
- platform: powercalc
|
||||
name: power_light_office_ceiling_2
|
||||
entity_id: light.office_ceiling_2
|
||||
linear:
|
||||
min_power: 0.5
|
||||
max_power: 5
|
||||
standby_usage: 0.2
|
||||
- platform: powercalc
|
||||
name: power_light_office_ceiling_3
|
||||
entity_id: light.office_ceiling_3
|
||||
linear:
|
||||
min_power: 0.5
|
||||
max_power: 5
|
||||
standby_usage: 0.2
|
||||
standby_usage: 0.6
|
||||
|
||||
automation:
|
||||
# Turn lights on (if not already on) when motion is detected and lux below threshold
|
||||
|
||||
Reference in New Issue
Block a user