186 files

This commit is contained in:
Home Assistant Version Control
2026-09-14 12:56:59 +00:00
parent 6be1e92941
commit bd8280defe
186 changed files with 14904 additions and 1087 deletions
+7 -3
View File
@@ -28,6 +28,7 @@ from homeassistant.helpers.entity_platform import async_get_platforms
import homeassistant.helpers.entity_registry as er
from homeassistant.helpers.event import async_call_later, async_track_time_interval
from homeassistant.helpers.reload import async_integration_yaml_config
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.typing import ConfigType
import voluptuous as vol
@@ -306,7 +307,8 @@ def register_services(hass: HomeAssistant) -> None:
async def _handle_change_gui_service(call: ServiceCall) -> None:
await change_gui_configuration(hass, call)
hass.services.async_register(
async_register_admin_service(
hass,
DOMAIN,
SERVICE_CHANGE_GUI_CONFIGURATION,
_handle_change_gui_service,
@@ -318,7 +320,8 @@ def register_services(hass: HomeAssistant) -> None:
discovery_manager = get_discovery_manager(hass)
await discovery_manager.update_library_and_rediscover()
hass.services.async_register(
async_register_admin_service(
hass,
DOMAIN,
SERVICE_UPDATE_LIBRARY,
_handle_update_library_service,
@@ -353,7 +356,8 @@ def register_services(hass: HomeAssistant) -> None:
setup_domain_groups(hass, global_config)
await create_standby_group(hass, global_config)
hass.services.async_register(
async_register_admin_service(
hass,
DOMAIN,
SERVICE_RELOAD,
_reload_config,
+1
View File
@@ -152,6 +152,7 @@ CONF_OR = "or"
CONF_PLAYBOOK = "playbook"
CONF_PLAYBOOKS = "playbooks"
CONF_POWER = "power"
CONF_POWER_CURVE = "power_curve"
CONF_POWER_FACTOR = "power_factor"
CONF_POWER_OFF = "power_off"
CONF_POWER_SENSOR_CATEGORY = "power_sensor_category"
@@ -34,6 +34,7 @@ async def async_setup_preview(hass: HomeAssistant) -> None:
websocket_api.async_register_command(hass, ws_start_preview)
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): f"{PREVIEW_NAME}/start_preview",
+1 -1
View File
@@ -22,5 +22,5 @@
"requirements": [
"numpy>=1.21.1"
],
"version": "v1.25.3"
"version": "v1.25.4"
}
@@ -47,6 +47,9 @@ LIBRARY_REPOSITORY_SEGMENTS = ("bramstroker", "homeassistant-powercalc")
LIBRARY_RESOURCE_DIRECTORY = "profile_library"
MAX_RESOURCE_SIZE = 10 * 1024 * 1024
MAX_MANIFEST_SIZE = 1024 * 1024
MAX_LIBRARY_SIZE = 10 * 1024 * 1024
MAX_PROFILE_RESOURCES = 256
MAX_PROFILE_DOWNLOAD_SIZE = 25 * 1024 * 1024
DOWNLOAD_CHUNK_SIZE = 64 * 1024
@@ -104,6 +107,48 @@ async def _read_capped(response: aiohttp.ClientResponse, limit: int, description
return b"".join(chunks)
def _validate_path_segment(value: object, description: str) -> str:
"""Validate a library identifier before using it as one directory component."""
if (
not isinstance(value, str)
or not value
or value in {".", ".."}
or any(character in value for character in ("/", "\\", ":", "\0"))
):
raise ProfileDownloadError(f"Library index contains an invalid {description}")
return value
def _validate_library_contents(contents: object) -> dict[str, Any]:
"""Validate the parts of library.json that determine local storage paths."""
if not isinstance(contents, dict) or not isinstance(manufacturers := contents.get("manufacturers"), list):
raise ProfileDownloadError("Library index contains invalid manufacturers")
for manufacturer in manufacturers:
if not isinstance(manufacturer, dict):
raise ProfileDownloadError("Library index contains an invalid manufacturer")
_validate_path_segment(manufacturer.get("dir_name"), "manufacturer directory")
models = manufacturer.get("models", [])
if not isinstance(models, list):
raise ProfileDownloadError("Library index contains invalid models")
for model in models:
if not isinstance(model, dict):
raise ProfileDownloadError("Library index contains an invalid model")
_validate_path_segment(model.get("id"), "model ID")
return cast(dict[str, Any], contents)
def _decode_library_json(data: bytes, description: str) -> dict[str, Any]:
"""Decode and validate a downloaded library index."""
try:
contents = json.loads(data)
except (JSONDecodeError, UnicodeDecodeError, RecursionError) as err:
raise ProfileDownloadError(f"{description} is not valid JSON") from err
return _validate_library_contents(contents)
def _resolve_resource_path(storage_path: str, resource_path: object) -> Path:
"""Resolve and validate a resource path within the profile storage directory."""
if not isinstance(resource_path, str) or not resource_path or "\0" in resource_path:
@@ -127,6 +172,10 @@ def _validate_resources(resources: object, storage_path: str) -> list[tuple[str,
"""Validate all resources in a remote profile response."""
if not isinstance(resources, list) or not all(isinstance(resource, dict) for resource in resources):
raise ProfileDownloadError("Remote profile response contains invalid resources")
if len(resources) > MAX_PROFILE_RESOURCES:
raise ProfileDownloadError(
f"Remote profile contains more than the maximum of {MAX_PROFILE_RESOURCES} resources",
)
return [
(_validate_resource_url(resource.get("url")), _resolve_resource_path(storage_path, resource.get("path")))
@@ -221,7 +270,7 @@ class RemoteLoader(Loader):
powercalc_version = AwesomeVersion(str(integration.version))
self._clear_caches()
self.library_contents = await self.load_library_json(prefer_cached)
self.library_contents = _validate_library_contents(await self.load_library_json(prefer_cached))
self.profile_hashes = await self.hass.async_add_executor_job(self._load_profile_hashes)
self.model_infos.clear()
@@ -342,9 +391,12 @@ class RemoteLoader(Loader):
if not os.path.exists(local_path):
return None
try:
with open(local_path) as f:
return cast(dict[str, Any], json.load(f))
except (JSONDecodeError, OSError) as err:
with open(local_path, "rb") as f:
data = f.read(MAX_LIBRARY_SIZE + 1)
if len(data) > MAX_LIBRARY_SIZE:
raise ProfileDownloadError(f"Local library is larger than the maximum of {MAX_LIBRARY_SIZE} bytes")
return _decode_library_json(data, "Local library")
except (OSError, ProfileDownloadError) as err:
_LOGGER.warning("Local library.json is unusable (%s), discarding it and downloading a fresh copy", err)
return None
@@ -372,14 +424,15 @@ class RemoteLoader(Loader):
f"Failed to download library.json, unexpected status code: {resp.status}",
)
data = await resp.read()
data = await _read_capped(resp, MAX_LIBRARY_SIZE, "Remote library")
except (TimeoutError, ClientError) as err:
raise ProfileDownloadError(f"Failed to download library.json: {err}") from err
library_contents = _decode_library_json(data, "Remote library")
await self.hass.async_add_executor_job(_save_resource, data, Path(local_path))
return cast(dict[str, Any], json.loads(data))
return library_contents
@async_cache
async def get_manufacturer_listing(
@@ -611,7 +664,16 @@ class RemoteLoader(Loader):
def get_storage_path(self, manufacturer: str, model: str) -> str:
"""Retrieve the storage path for a given manufacturer and model."""
return str(self.hass.config.path(STORAGE_DIR, BUILT_IN_LIBRARY_DIR, manufacturer, model))
manufacturer = _validate_path_segment(manufacturer, "manufacturer directory")
model = _validate_path_segment(model, "model ID")
storage_root = Path(self.hass.config.path(STORAGE_DIR, BUILT_IN_LIBRARY_DIR)).resolve()
try:
storage_path = (storage_root / manufacturer / model).resolve()
except (OSError, RuntimeError, ValueError) as err:
raise ProfileDownloadError("Remote profile has an invalid storage path") from err
if not storage_path.is_relative_to(storage_root):
raise ProfileDownloadError("Remote profile storage path is outside the profile library")
return str(storage_path)
async def download_with_retry(
self,
@@ -672,12 +734,30 @@ class RemoteLoader(Loader):
# Download the files
downloaded_resources: list[tuple[bytes, Path]] = []
downloaded_size = 0
for url, destination in validated_resources:
async with session.get(url, allow_redirects=False) as resp:
if resp.status != 200:
raise ProfileDownloadError(f"Failed to download github URL: {url}")
contents = await _read_capped(resp, MAX_RESOURCE_SIZE, f"Remote profile resource {url}")
remaining_size = MAX_PROFILE_DOWNLOAD_SIZE - downloaded_size
if remaining_size <= 0:
raise ProfileDownloadError(
f"Remote profile is larger than the maximum of {MAX_PROFILE_DOWNLOAD_SIZE} bytes",
)
try:
contents = await _read_capped(
resp,
min(MAX_RESOURCE_SIZE, remaining_size),
f"Remote profile resource {url}",
)
except ProfileDownloadError as err:
if remaining_size < MAX_RESOURCE_SIZE:
raise ProfileDownloadError(
f"Remote profile is larger than the maximum of {MAX_PROFILE_DOWNLOAD_SIZE} bytes",
) from err
raise
downloaded_size += len(contents)
downloaded_resources.append((contents, destination))
await self.hass.async_add_executor_job(_save_resources, downloaded_resources)
@@ -28,6 +28,7 @@ from homeassistant.helpers.typing import ConfigType
from custom_components.powercalc.const import (
BUILT_IN_LIBRARY_DIR,
CONF_CALIBRATE,
CONF_ENERGY_SENSOR_NAMING,
CONF_MAX_POWER,
CONF_MIN_POWER,
@@ -309,9 +310,11 @@ class PowerProfile:
if self.only_self_usage:
return False
return self.is_strategy_supported(
CalculationStrategy.LINEAR,
) and not self._json_data.get("linear_config")
if not self.is_strategy_supported(CalculationStrategy.LINEAR):
return False
linear_config = self._json_data.get("linear_config") or {}
return CONF_MAX_POWER not in linear_config and not linear_config.get(CONF_CALIBRATE)
@property
def device_type(self) -> DeviceType | None:
@@ -264,15 +264,28 @@ async def resolve_entity_ids_recursively(
entry: ConfigEntry,
device_class: SensorDeviceClass,
resolved_ids: set[str] | None = None,
seen_entry_ids: set[str] | None = None,
) -> set[str]:
"""Get all the entity IDs for the current group and all the subgroups."""
"""Get all the entity IDs for the current group and all the subgroups.
Groups can reference each other as a subgroup, either in a cycle or as two groups sharing
the same subgroup. `seen_entry_ids` makes sure every group is only visited once, so a cycle
does not recurse until the stack runs out.
"""
if resolved_ids is None:
resolved_ids = set()
if seen_entry_ids is None:
seen_entry_ids = set()
if entry.entry_id in seen_entry_ids:
_LOGGER.debug("Group config entry %s already resolved, skipping", entry.entry_id)
return resolved_ids
seen_entry_ids.add(entry.entry_id)
_add_member_entry_ids(hass, entry, device_class, resolved_ids)
_add_specified_sensors(entry, device_class, resolved_ids)
await _add_include_based_sensors(hass, entry, device_class, resolved_ids)
await _add_subgroup_entities(hass, entry, device_class, resolved_ids)
await _add_subgroup_entities(hass, entry, device_class, resolved_ids, seen_entry_ids)
return resolved_ids
@@ -331,6 +344,7 @@ async def _add_subgroup_entities(
entry: ConfigEntry,
device_class: SensorDeviceClass,
resolved_ids: set[str],
seen_entry_ids: set[str],
) -> None:
"""Recursively add entities from subgroups."""
subgroups = entry.data.get(CONF_SUB_GROUPS)
@@ -343,7 +357,7 @@ async def _add_subgroup_entities(
_LOGGER.error("Subgroup config entry not found: %s", subgroup_entry_id)
continue
await resolve_entity_ids_recursively(hass, subgroup_entry, device_class, resolved_ids)
await resolve_entity_ids_recursively(hass, subgroup_entry, device_class, resolved_ids, seen_entry_ids)
@callback
@@ -1,12 +1,13 @@
from decimal import Decimal
import logging
from typing import Any
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.const import CONF_NAME, UnitOfPower
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, UnitOfPower
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
@@ -14,6 +15,8 @@ from homeassistant.helpers.typing import ConfigType
from custom_components.powercalc.common import create_source_entity
from custom_components.powercalc.const import (
ATTR_MEMBERS,
ATTR_STATE,
CONF_CREATE_ENERGY_SENSORS,
CONF_POWER_SENSOR_PRECISION,
DATA_STANDBY_POWER_SENSORS,
@@ -77,6 +80,7 @@ class StandbyPowerSensor(PowerSensor, SensorEntity):
self._recalculate,
),
)
await self._recalculate()
async def _recalculate(self) -> None:
"""Calculate sum of all power sensors in standby, and update the state of the sensor."""
@@ -90,3 +94,18 @@ class StandbyPowerSensor(PowerSensor, SensorEntity):
else:
self._attr_native_value = None
self.async_schedule_update_ha_state(True)
def debug_group(self) -> dict[str, Any]:
"""Return the current standby contribution of each tracked power sensor."""
members = {
entity_id: {
ATTR_STATE: str(round(power, self._rounding_digits)),
ATTR_UNIT_OF_MEASUREMENT: self.native_unit_of_measurement,
}
for entity_id, power in sorted(self.standby_sensors.items())
}
return {
ATTR_STATE: str(self.state),
ATTR_UNIT_OF_MEASUREMENT: self.native_unit_of_measurement,
ATTR_MEMBERS: members,
}
@@ -12,10 +12,15 @@ import voluptuous as vol
from custom_components.powercalc.common import SourceEntity
from custom_components.powercalc.const import (
CONF_CALIBRATE,
CONF_COMPOSITE,
CONF_GAMMA_CURVE,
CONF_MAX_POWER,
CONF_MIN_POWER,
CONF_MODE,
CONF_MULTI_SWITCH,
CONF_POWER,
CONF_POWER_CURVE,
CONF_POWER_OFF,
CONF_POWER_TEMPLATE,
CONF_STANDBY_POWER,
@@ -118,7 +123,7 @@ class PowerCalculatorStrategyFactory:
power_profile: PowerProfile | None,
) -> LinearStrategy:
"""Create the linear strategy."""
linear_config = self._get_strategy_config(CalculationStrategy.LINEAR, config, power_profile)
linear_config = self._get_linear_config(config, power_profile)
return LinearStrategy(
linear_config,
@@ -127,6 +132,55 @@ class PowerCalculatorStrategyFactory:
config.get(CONF_STANDBY_POWER),
)
@staticmethod
def _get_linear_config(config: ConfigType, power_profile: PowerProfile | None) -> ConfigType:
"""Combine an incomplete profile configuration with user supplied power values."""
user_config = cast(ConfigType | None, config.get(CalculationStrategy.LINEAR))
if power_profile is None or not power_profile.is_strategy_supported(CalculationStrategy.LINEAR):
if user_config is not None:
return user_config
raise StrategyConfigurationError("No linear configuration supplied")
profile_config = dict(power_profile.linear_config or {})
# Preserve the existing behavior for complete profiles: an explicitly supplied
# user configuration replaces the profile configuration entirely.
if not power_profile.needs_linear_config:
return user_config if user_config is not None else profile_config
# Smart dimmer profiles without load values can be used from YAML to measure
# only the dimmer's own consumption. Keep that behavior when the profile adds
# defaults such as gamma_curve but the user omits the linear configuration.
if not user_config:
return {
CONF_MIN_POWER: 0,
CONF_MAX_POWER: 0,
**profile_config,
}
linear_config = {
**profile_config,
**user_config,
}
PowerCalculatorStrategyFactory._remove_overridden_profile_curves(linear_config, user_config)
return linear_config
@staticmethod
def _remove_overridden_profile_curves(linear_config: ConfigType, user_config: ConfigType) -> None:
"""Remove profile curve options superseded by explicit user configuration."""
if CONF_CALIBRATE in user_config:
if CONF_GAMMA_CURVE not in user_config:
linear_config.pop(CONF_GAMMA_CURVE, None)
if CONF_POWER_CURVE not in user_config:
linear_config.pop(CONF_POWER_CURVE, None)
elif CONF_GAMMA_CURVE in user_config:
linear_config.pop(CONF_POWER_CURVE, None)
elif CONF_POWER_CURVE in user_config:
linear_config.pop(CONF_GAMMA_CURVE, None)
def _create_fixed(
self,
source_entity: SourceEntity,
+63 -12
View File
@@ -1,3 +1,4 @@
from bisect import bisect_left, bisect_right
from decimal import Decimal
import logging
from typing import Any
@@ -24,6 +25,7 @@ from custom_components.powercalc.const import (
CONF_MAX_POWER,
CONF_MIN_POWER,
CONF_POWER,
CONF_POWER_CURVE,
CONF_VALUE,
)
from custom_components.powercalc.errors import StrategyConfigurationError
@@ -41,6 +43,11 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_MIN_POWER): vol.Coerce(float),
vol.Optional(CONF_MAX_POWER): vol.Coerce(float),
vol.Optional(CONF_GAMMA_CURVE): vol.Coerce(float),
vol.Optional(CONF_POWER_CURVE): vol.All(
cv.ensure_list,
[vol.Match(r"^(?:0(?:\.\d+)?|1(?:\.0+)?) -> (?:0(?:\.\d+)?|1(?:\.0+)?)$")],
vol.Length(min=2),
),
vol.Optional(CONF_ATTRIBUTE): cv.string,
},
)
@@ -71,11 +78,13 @@ class LinearStrategy(PowerCalculationStrategyInterface):
self._initialized: bool = False
self._missing_attribute_warned: bool = False
self._calibration: list[tuple[int, float]] | None = None
self._power_curve: list[tuple[float, float]] | None = None
async def initialize(self) -> None:
"""Initialize the strategy, called once on creation."""
self._value_entity = await self.get_value_entity()
self._calibration = self.create_calibrate_list()
self._power_curve = self.create_power_curve_list()
async def calculate(self, entity_state: State) -> Decimal | None:
"""Calculate the current power consumption."""
@@ -89,8 +98,7 @@ class LinearStrategy(PowerCalculationStrategyInterface):
if value is None:
return None
min_calibrate = self.get_min_calibrate(value)
max_calibrate = self.get_max_calibrate(value)
min_calibrate, max_calibrate = self.get_calibration_segment(value)
min_value = min_calibrate[0]
max_value = max_calibrate[0]
@@ -108,11 +116,8 @@ class LinearStrategy(PowerCalculationStrategyInterface):
value_range = max_value - min_value
power_range = max_power - min_power
gamma_curve = self._config.get(CONF_GAMMA_CURVE) or 1
relative_value = (value - min_value) / value_range
power = power_range * relative_value**gamma_curve + min_power
power = power_range * self.apply_curve(relative_value) + min_power
return Decimal(power)
@@ -120,13 +125,22 @@ class LinearStrategy(PowerCalculationStrategyInterface):
"""Return if this strategy is enabled based on entity state."""
return not (self._source_entity.domain == media_player.DOMAIN and entity_state.state != STATE_PLAYING)
def get_min_calibrate(self, value: int) -> tuple[int, float]:
"""Get closest lower value from calibration table."""
return min(self._calibration or (), key=lambda v: (v[0] > value, value - v[0]))
def get_calibration_segment(self, value: int) -> tuple[tuple[int, float], tuple[int, float]]:
"""Get the two calibration points to interpolate between, in ascending order.
def get_max_calibrate(self, value: int) -> tuple[int, float]:
"""Get closest higher value from calibration table."""
return max(self._calibration or (), key=lambda v: (v[0] > value, value - v[0]))
Values inside the table use the segment they fall in. Values outside it are
extrapolated along the chord between the first and the last point.
"""
calibration = self._calibration
if not calibration:
raise StrategyConfigurationError("Linear strategy has not been initialized")
if value < calibration[0][0] or value > calibration[-1][0]:
return calibration[0], calibration[-1]
index = bisect_right(calibration, value, key=lambda point: point[0])
index = min(max(index, 1), len(calibration) - 1)
return calibration[index - 1], calibration[index]
def create_calibrate_list(self) -> list[tuple[int, float]]:
"""Build a table of calibration values."""
@@ -156,6 +170,43 @@ class LinearStrategy(PowerCalculationStrategyInterface):
return sorted(calibration_list, key=lambda tup: tup[0])
def create_power_curve_list(self) -> list[tuple[float, float]] | None:
"""Build a table of normalized power curve values."""
power_curve = self._config.get(CONF_POWER_CURVE)
if not power_curve:
return None
points = []
for line in power_curve:
value, power = line.split(" -> ")
points.append((float(value), float(power)))
return sorted(points, key=lambda point: point[0])
def apply_curve(self, relative_value: float) -> float:
"""Apply a configured gamma or normalized power curve."""
gamma_curve = self._config.get(CONF_GAMMA_CURVE)
if gamma_curve:
if relative_value < 0:
# A negative base raised to a fractional exponent is complex. Below the
# calibrated range there is no curve to apply, so stay linear.
return relative_value
return float(relative_value ** float(gamma_curve))
if self._power_curve:
if relative_value <= self._power_curve[0][0]:
return self._power_curve[0][1]
if relative_value >= self._power_curve[-1][0]:
return self._power_curve[-1][1]
max_index = bisect_left(self._power_curve, relative_value, key=lambda point: point[0])
min_point = self._power_curve[max_index - 1]
max_point = self._power_curve[max_index]
value_range = max_point[0] - min_point[0]
curve_range = max_point[1] - min_point[1]
return curve_range * ((relative_value - min_point[0]) / value_range) + min_point[1]
return relative_value
def get_entity_value_range(self) -> tuple[int, int]:
"""Get the min/max range for a given entity domain."""
if self.get_initialized_value_entity().domain == light.DOMAIN:
+6 -6
View File
@@ -5,7 +5,6 @@ from dataclasses import dataclass
from decimal import Decimal
from enum import StrEnum
from functools import partial
import gzip
import logging
import os
from typing import Any, TextIO, cast
@@ -30,6 +29,7 @@ from custom_components.powercalc.errors import (
)
from custom_components.powercalc.power_profile.power_profile import PowerProfile
from .profile_data import open_profile_csv
from .strategy_interface import PowerCalculationStrategyInterface
_LOGGER = logging.getLogger(__name__)
@@ -78,7 +78,7 @@ class LutRegistry:
self._hass = hass
self._lut_entries: dict[_CacheKey, _LutEntry] = {}
self._effect_entries: dict[_CacheKey, _EffectEntry] = {}
self._supported_modes: dict[tuple[str, str, str], set[LookupMode]] = {}
self._supported_modes: dict[tuple[str, str, str | None, str], set[LookupMode]] = {}
async def get_lookup_entry(
self,
@@ -107,7 +107,7 @@ class LutRegistry:
async def get_supported_modes(self, power_profile: PowerProfile) -> set[LookupMode]:
"""Return the LUT modes supported by the profile."""
cache_key = (power_profile.manufacturer, power_profile.model, "supported_modes")
cache_key = (power_profile.manufacturer, power_profile.model, power_profile.sub_profile, "supported_modes")
supported_modes = self._supported_modes.get(cache_key)
if supported_modes is None:
supported_modes = set()
@@ -184,11 +184,11 @@ class LutRegistry:
gzip_path = f"{path}.gz"
if os.path.exists(gzip_path):
_LOGGER.debug("Loading LUT data file: %s", gzip_path)
return gzip.open(gzip_path, "rt")
return open_profile_csv(gzip_path)
if os.path.exists(path):
_LOGGER.debug("Loading LUT data file: %s", path)
return open(path)
return open_profile_csv(path)
raise LutFileNotFoundError(f"Data file not found: {path}")
@@ -232,7 +232,7 @@ class LutStrategy(PowerCalculationStrategyInterface):
return None
effect = attrs.get(ATTR_EFFECT)
if effect and str(effect).lower() not in ("off", "none", "white"):
if effect and str(effect).lower() not in ("off", "none", "white", "default", "mode color"):
return await self._calculate_effect_power(entity_state, str(effect), brightness)
lut_mode = LookupMode.from_color_mode(color_mode)
@@ -4,7 +4,6 @@ import csv
from dataclasses import dataclass
from datetime import datetime, timedelta
from decimal import Decimal
import gzip
import logging
import os
@@ -25,6 +24,7 @@ from custom_components.powercalc.const import (
)
from custom_components.powercalc.errors import StrategyConfigurationError
from .profile_data import open_profile_csv
from .strategy_interface import PowerCalculationStrategyInterface
CONFIG_SCHEMA = vol.All(
@@ -222,8 +222,7 @@ class PlaybookStrategy(PowerCalculationStrategyInterface):
f"Playbook file '{file_path}' does not exist",
)
actual_path = file_path if os.path.exists(file_path) else f"{file_path}.gz"
open_func = gzip.open if actual_path.endswith(".gz") else open
with open_func(actual_path, mode="rt") as csv_file:
with open_profile_csv(actual_path) as csv_file:
csv_reader = csv.reader(csv_file)
entries = []
for row in csv_reader:
+120 -120
View File
@@ -11,7 +11,7 @@
"already_configured": "Sensor is reeds geconfigureerd, specifieer een uniek ID"
},
"error": {
"cost_price_mandatory": "U moet een energieprijs of een sensor van de energieprijs leveren",
"cost_price_mandatory": "U moet een energieprijs of een energieprijssensor opgeven",
"daily_energy_mandatory": "Je moet minimaal waarde of waarde template opgeven",
"entity_mandatory": "Je moet verplicht een entiteit opgeven voor iedere andere strategie dan playbook",
"fixed_mandatory": "Je dient minimaal een van de volgende velden te definiëren: Vermogen, Vermogen template of Vermogen per status",
@@ -48,7 +48,7 @@
},
"daily_energy": {
"data": {
"create_utility_meters": "Creëer utiliteit meters",
"create_utility_meters": "Utiliteit meters aanmaken",
"daily_energy_value": "Dagelijkse energiewaarde",
"name": "Naam",
"on_time": "Tijd aan per dag",
@@ -59,10 +59,10 @@
"value_template": "Waarde template"
},
"data_description": {
"on_time": "Standaard 1 dag indien niet gedefinieerd. altijd aan",
"update_frequency": "tijd in seconden tussen status updates van de sensor"
"on_time": "Indien leeg, is de standaardwaarde 1 dag. Altijd aan",
"update_frequency": "Tijd in seconden tussen statusupdates van de sensor"
},
"title": "Maak een sensor voor vast dagelijks energieverbruik"
"title": "Dagelijkse verbruiksensor aanmaken"
},
"energy_options": {
"title": "Energieopties",
@@ -80,13 +80,13 @@
"group_domain": {
"data": {
"name": "Naam",
"create_energy_sensor": "Creëer energie sensoren",
"create_cost_sensor": "Maak een kostensensor",
"create_utility_meters": "Creëer utiliteit meters",
"create_energy_sensor": "Energiesensor aanmaken",
"create_cost_sensor": "Kostensensor aanmaken",
"create_utility_meters": "Utiliteits meter aanmaken",
"domain": "Entiteit domein",
"exclude_entities": "Entiteiten uitsluiten"
},
"title": "Maak een domein gebaseerde groep sensor"
"title": "Domeingebaseerde groepsensor aanmaken"
},
"fixed": {
"data": {
@@ -107,23 +107,23 @@
"data": {
"power_sensor_naming": "Patroon voor naam vermogenssensor",
"power_sensor_friendly_naming": "Patroon voor vriendelijke naam vermogenssensor",
"power_sensor_category": "Vermogen sensor categorie",
"power_sensor_category": "Vermogenssensor categorie",
"power_sensor_precision": "Precisie van vermogenssensor"
}
},
"features": {
"name": "Sensoren aanmaken",
"name": "Sensoren maken",
"data": {
"create_energy_sensors": "Creëer energie sensoren",
"create_cost_sensors": "Maak kostensensoren",
"create_standby_group": "Maak standby-groep",
"create_utility_meters": "Creëer utiliteit meters"
"create_energy_sensors": "Energiesensoren aanmaken",
"create_cost_sensors": "Kostensensoren aanmaken",
"create_standby_group": "Stand-bygroep aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken"
},
"data_description": {
"create_energy_sensors": "Of powercalc kWh sensoren moet maken",
"create_cost_sensors": "Of powercalc kostensensoren moet creëren. Vereist dat in de volgende stappen een energieprijs wordt geconfigureerd",
"create_standby_group": "Maak groep met een totaal van alle standby en eigen gebruik van IOT apparaten",
"create_utility_meters": "Laat powercalc utiliteit meters maken. Deze resetten dagelijks, elk uur, etc."
"create_energy_sensors": "Moet Powercalc kWh sensoren aanmaken",
"create_cost_sensors": "Moet Powercalc kostensensoren aanmaken. Vereist dat in de volgende stappen een energieprijs wordt geconfigureerd",
"create_standby_group": "Stand-by- en eigen verbruiksgroep van IoT-apparaten aanmaken",
"create_utility_meters": "Laat Powercalc utiliteit meters aanmaken. Deze resetten dagelijks, elk uur, etc."
}
},
"advanced": {
@@ -133,20 +133,20 @@
"ignore_unavailable_state": "Negeer onbeschikbaar status",
"include_non_powercalc_sensors": "Neem niet-powercalc sensoren mee",
"disable_extended_attributes": "Extra attributen uitschakelen",
"disable_library_download": "Schakel download van externe bibliotheek uit"
"disable_library_download": "Schakel downloaden van externe bibliotheek uit"
},
"data_description": {
"enable_analytics": "Sta Powercalc toe om anonieme en geaggregeerde gebruiksstatistieken te verzenden om de integratie te helpen verbeteren",
"ignore_unavailable_state": "Houd Powercalc sensoren beschikbaar, zelfs als de bron entiteit niet beschikbaar is",
"include_non_powercalc_sensors": "Bepaal of je niet-powercalc sensoren wilt opnemen in groepen",
"disable_extended_attributes": "Schakel alle extra attributen uit die Powercalc aan de energie, energie en groepsentiteit states toevoegt. Dit helpt de grootte van de database klein te houden",
"disable_library_download": "Uitschakelen van de Powercalc bibliotheek download functie"
"disable_extended_attributes": "Schakel alle extra attributen uit die Powercalc toevoegt aan de statussen van de vermogens-, energie- en groepsentiteiten. Dit helpt om de database klein te houden",
"disable_library_download": "Schakel downloaden van de Powercalc-bibliotheek uit"
}
}
}
},
"global_configuration_cost": {
"title": "Kosten opties",
"title": "Kostenopties",
"description": "Configureer de energieprijs die wordt gebruikt om kostensensoren te berekenen. Geef een vaste prijs op of een sensor die de huidige prijs per kWh levert. Zie [documentatie]({docs_uri}) voor meer informatie",
"sections": {
"cost_pricing": {
@@ -155,13 +155,13 @@
"energy_price": "Vaste energieprijs",
"energy_price_multiplier": "Multiplier voor energieprijzen",
"energy_price_sensor": "Energieprijssensor",
"energy_price_surcharge": "Toeslag energieprijs"
"energy_price_surcharge": "Energieprijstoeslag"
},
"data_description": {
"energy_price": "Een vaste prijs per kWh in uw lokale valuta",
"energy_price_multiplier": "Vermenigvuldiger toegepast na de toeslag. Gebruik dit voor procentuele belastingen of toeslagen, bijvoorbeeld 1,21 voor 21%",
"energy_price_sensor": "Een sensor die de actuele energieprijs per kWh weergeeft (bijvoorbeeld door een dynamische tariefintegratie). Laat de vaste prijs leeg om deze sensor te gebruiken",
"energy_price_surcharge": "Extra vast bedrag per kWh toegevoegd aan de vaste prijs of prijssensorwaarde"
"energy_price_multiplier": "Vermenigvuldigingsfactor wordt na de toeslag toegepast. Gebruik dit voor belastingen of heffingen op basis van percentages, bijvoorbeeld 1,21 voor 21%",
"energy_price_sensor": "Een sensor die de actuele energieprijs per kWh toont (bijvoorbeeld via een dynamische tariefintegratie). Laat de vaste prijs leeg om deze sensor te gebruiken",
"energy_price_surcharge": "Extra vast bedrag per kWh dat wordt toegevoegd aan de vaste prijs of de waarde van de prijssensor"
}
},
"cost_naming": {
@@ -171,25 +171,25 @@
"cost_sensor_naming": "Naampatroon van kostensensor"
},
"data_description": {
"cost_sensor_friendly_naming": "Patroon voor de beschrijvende naam van de kostensensor. De bronnaam wordt ingevoegd op de plaatsaanduidingspositie",
"cost_sensor_naming": "Patroon dat wordt gebruikt om de naam van de kostensensor en de entiteits-ID samen te stellen. De bronnaam wordt ingevoegd op de plaatsaanduidingspositie"
"cost_sensor_friendly_naming": "Patroon voor de weergavenaam van de kostensensor. De naam van de bron-entiteit wordt ingevoegd op de plek van de placeholder",
"cost_sensor_naming": "Patroon dat wordt gebruikt om de naam en de entiteit-id van de kostensensor op te bouwen. De bronnaam wordt ingevoegd op de plek van de placeholder"
}
}
}
},
"global_configuration_cost_apply": {
"title": "Pas kostensensoren toe",
"description": "U heeft de instelling voor globale kostensensoren gewijzigd. Wilt u deze wijziging toepassen op alle bestaande powercalc-sensoren die zijn gemaakt via de GUI?",
"title": "Kostensensoren toepassen",
"description": "De instellingen voor globale kostensensoren zijn gewijzigd. Wilt u deze wijziging toepassen op alle bestaande Powercalc-sensoren die zijn gemaakt via de GUI?",
"data": {
"apply_to_all": "Toepassen op bestaande sensoren"
"apply_to_all": "Op bestaande sensoren toepassen"
},
"data_description": {
"apply_to_all": "Werk elke bestaande GUI-powercalc-sensor bij zodat deze overeenkomt met de nieuwe instelling voor kostensensoren"
"apply_to_all": "Werk elke bestaande GUI Powercalc-sensor bij zodat deze overeenkomt met de nieuwe instelling voor kostensensoren"
}
},
"global_configuration_discovery": {
"title": "Ontdekkingsopties",
"description": "Powercalc kan uw systeem scannen op apparaten en entiteiten die vermogensprofielen hebben in de ingebouwde bibliotheek. Met de onderstaande opties kunt u dit gedrag fijnafstemmen.",
"description": "Powercalc kan uw systeem scannen op apparaten en entiteiten die vermogensprofielen hebben in de ingebouwde bibliotheek. Met de onderstaande opties kunt u dit gedrag verfijnen.",
"data": {
"enabled": "Ontdekking inschakelen",
"exclude_device_types": "Uitgesloten apparaattypes",
@@ -203,10 +203,10 @@
"global_configuration_energy": {
"title": "Energie opties",
"data": {
"energy_integration_method": "Energie integratie methode",
"energy_sensor_category": "Energie sensor categorie",
"energy_sensor_friendly_naming": "Energie sensor vriendelijk naampatroon",
"energy_sensor_naming": "Energie sensor naampatroon",
"energy_integration_method": "Energie-integratiemethode",
"energy_sensor_category": "Energiesensor categorie",
"energy_sensor_friendly_naming": "Energiesensor vriendelijk naampatroon",
"energy_sensor_naming": "Energiesensor naampatroon",
"energy_sensor_precision": "Nauwkeurigheid energiesensor",
"energy_sensor_unit_prefix": "Voorvoegsel van de energiesensor eenheid"
}
@@ -243,7 +243,7 @@
"members": {
"name": "Leden",
"data": {
"group_member_sensors": "Individuele Powercalc sensoren",
"group_member_sensors": "Individuele Powercalc-sensoren",
"group_member_devices": "Apparaten",
"group_power_entities": "Additionele vermogen entiteiten",
"group_energy_entities": "Additionele energie entiteiten",
@@ -252,13 +252,13 @@
"floor": "Verdieping"
},
"data_description": {
"group_member_sensors": "Powercalc sensoren die je wil toevoegen aan de groep",
"group_member_sensors": "Powercalc-sensoren die je wil toevoegen aan de groep",
"group_member_devices": "Energie- en stroomentiteiten van de geselecteerde apparaten toevoegen aan de groep",
"group_power_entities": "Additionele vermogen entiteiten (W)",
"group_energy_entities": "Additionele energie entiteiten (kWh)",
"sub_groups": "Alle onderliggende sensoren van de geselecteerde sub-groepen worden ook automatisch aan deze groep toegevoegd.",
"area": "Voeg alle powercalc sensoren van de gespecificeerde ruimte toe",
"floor": "Voeg alle powercalc sensoren van de gespecificeerde verdieping toe"
"sub_groups": "Alle onderliggende sensoren van de geselecteerde sub-groepen worden ook automatisch aan deze groep toegevoegd",
"area": "Voeg alle Powercalc-sensoren van de gespecificeerde ruimte toe",
"floor": "Voeg alle Powercalc-sensoren van de gespecificeerde verdieping toe"
}
},
"options": {
@@ -269,25 +269,25 @@
"include_non_powercalc_sensors": "Niet-powercalc sensoren meenemen",
"force_calculate_group_energy": "Forceer berekenen energie",
"group_energy_start_at_zero": "Start op nul",
"create_energy_sensor": "Creëer energie sensoren",
"create_utility_meters": "Creëer utiliteit meters"
"create_energy_sensor": "Energiesensoren aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken"
},
"data_description": {
"device": "Voeg de entiteiten toe aan een bestaand HA apparaat",
"device": "Powercalc-groepsentiteiten toevoegen aan bestaand apparaat",
"include_non_powercalc_sensors": "Bepaal of je niet-powercalc sensoren wilt opnemen bij gebruik van de ruimte optie",
"force_calculate_group_energy": "Standaard worden de energiesensoren van de individuele sensoren opgeteld. Schakel deze optie in om een Riemann Sum sensor te forceren",
"group_energy_start_at_zero": "Wanneer ingeschakeld, start de energiesensor bij nul wanneer de sensor is aangemaakt. Anders worden alle huidige onderliggende energiesensor waarden opgeteld.",
"create_energy_sensor": "Laat powercalc een kWh meter maken"
"group_energy_start_at_zero": "Wanneer ingeschakeld, start de energiesensor bij nul wanneer deze is aangemaakt. Anders worden alle huidige onderliggende energiesensorwaarden opgeteld",
"create_energy_sensor": "Laat Powercalc een kWh meter aanmaken"
}
}
},
"title": "Creëer een groep sensor"
"title": "Groepsensor aanmaken"
},
"group_subtract": {
"data": {
"create_energy_sensor": "Creëer energie sensoren",
"create_cost_sensor": "Maak een kostensensor",
"create_utility_meters": "Creëer utiliteit meters",
"create_energy_sensor": "Energiesensoren aanmaken",
"create_cost_sensor": "Kostensensor aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken",
"entity_id": "Basis entiteit",
"name": "Naam",
"subtract_entities": "Entiteiten aftrekken"
@@ -302,18 +302,18 @@
"data": {
"main_power_sensor": "Hoofd vermogen sensor (P1)",
"group_tracked_auto": "Entiteiten automatisch toegevoegd",
"create_energy_sensor": "Creëer energie sensor",
"create_cost_sensor": "Maak een kostensensor",
"create_utility_meters": "Creëer utiliteit meters"
"create_energy_sensor": "Energiesensor aanmaken",
"create_cost_sensor": "Kostensensor aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken"
},
"data_description": {
"main_power_sensor": "Selecteer de vermogenssensor die uw totale energieverbruik bevat. Indien leeg wordt alleen een tracked groep gemaakt",
"main_power_sensor": "Selecteer de vermogenssensor die uw totale vermogensverbruik bevat. Indien leeg, wordt er alleen een gevolgde vermogenssensor aangemaakt",
"group_tracked_auto": "Powercalc voegt automatisch alle vermogen sensoren toe aan de tracked groep, uitgezonderd powercalc groepen. Schakel deze uit als u handmatig de bijgehouden entiteiten wilt definiëren.",
"create_energy_sensor": "Laat Powercalc een kWh meter maken",
"create_utility_meters": "Laat powercalc utiliteit meters maken. Deze resetten dagelijks, uurlijks etc."
"create_energy_sensor": "Laat Powercalc een kWh meter aanmaken",
"create_utility_meters": "Laat Powercalc utiliteit meters aanmaken. Deze resetten dagelijks, uurlijks etc."
},
"description": "Maak een groepssensor aan voor alle bijgehouden en niet bijgehouden energie",
"title": "Maak een bijgehouden vermogensensor"
"description": "Groepsensor voor gevolgd en ongevolgd vermogen aanmaken",
"title": "Bijgehouden vermogensensor aanmaken"
},
"group_tracked_untracked_manual": {
"data": {
@@ -402,7 +402,7 @@
"group_tracked_untracked": "Bijgehouden/niet bijgehouden vermogen"
},
"title": "Kies het type groep",
"description": "Selecteer het type groepssensor dat je wilt aanmaken. Kies een domein gebaseerde groep als u alle entiteiten van een specifiek domein wilt groeperen of een sensor opsomming wilt maken van al uw energie sensoren."
"description": "Selecteer het type groepsensor dat je wilt aanmaken. Kies een domein gebaseerde groep als u alle entiteiten van een specifiek domein wilt groeperen of een sensor opsomming wilt maken van al uw energie sensoren"
},
"multi_switch": {
"data": {
@@ -449,7 +449,7 @@
"calculation_enabled_condition": "De geconfigureerde vermogen calculatie strategie wordt alleen uitgevoerd indien dit template evalueert naar 1, anders zal de sensor 0 tonen",
"energy_filter_outlier_enabled": "Schakel filtering van uitschieters in de energiesensor in",
"energy_filter_outlier_max_step": "Maximale verwachte stap in vermogenswaarden (in watt) voor het uitschieter filter",
"ignore_unavailable_state": "Zet dit aan wanneer je de vermogen sensor beschikbaar wil houden ongeacht de bron sensor niet beschikbaar is.",
"ignore_unavailable_state": "Zet dit aan wanneer je de vermogensensor beschikbaar wil houden ongeacht de bron sensor niet beschikbaar is",
"multiply_factor": "Vermenigvuldigd het gecalculeerde vermogen bij deze ratio. Kan handig zijn voor lamp groepen.",
"multiply_factor_standby": "Pas vermenigvuldigingsfactor toe op standby vermogen",
"unavailable_power": "Vermogen in W wanneer de bron sensor onbeschikbaar is"
@@ -459,7 +459,7 @@
},
"real_power": {
"data": {
"create_utility_meters": "Creëer utiliteit meters",
"create_utility_meters": "Utiliteit meters aanmaken",
"entity_id": "Vermogen sensor id",
"name": "Naam",
"device": "Apparaat"
@@ -467,10 +467,10 @@
"data_description": {
"create_utility_meters": "Laat powercalc utiliteit meters maken. Deze resetten dagelijks, uurlijks etc.",
"name": "Basisnaam voor de gegenereerde vermogen- en utiliteitsmeter. Volledige entiteit naam zal worden ingesteld afhankelijk van de energy_sensor_naming instelling",
"device": "Voeg de gecreëerde energiesensor toe aan een specifiek apparaat"
"device": "Voeg de aangemaakte energiesensor toe aan een specifiek apparaat"
},
"description": "Momenteel kunnen specifieke instellingen alleen globaal worden geconfigureerd",
"title": "Maak een energiesensor voor een bestaande stroomsensor"
"title": "Energiesensor voor een bestaande stroomsensor aanmaken"
},
"cost": {
"data": {
@@ -481,7 +481,7 @@
"energy_sensor_id": "De bestaande energiesensor (kWh) waarvoor de kosten worden berekend. Dit kan een sensor van elke integratie zijn, niet alleen Powercalc",
"name": "Basisnaam voor de kostensensor. De volledige entiteitsnaam wordt ingesteld volgens de instelling cost_sensor_naming"
},
"description": "Maak een kostensensor voor een bestaande energiesensor. De energieprijs wordt overgenomen uit de globale Powercalc-configuratie.",
"description": "Kostensensor voor een bestaande energiesensor aanmaken. De energieprijs wordt overgenomen uit de globale Powercalc-configuratie.",
"sections": {
"cost_pricing": {
"name": "Prijs overschrijven",
@@ -499,7 +499,7 @@
}
}
},
"title": "Kostensensor maken"
"title": "Kostensensor aanmaken"
},
"sub_profile": {
"data": {
@@ -538,7 +538,7 @@
"global_configuration": "Globale configuratie",
"menu_group": "Groep",
"menu_library": "Virtueel vermogen (bibliotheek)",
"real_power": "Energie gebaseerd op bestaande vermogen sensor",
"real_power": "Energie gebaseerd op bestaande vermogensensor",
"virtual_power": "Virtueel vermogen (handmatig)"
},
"title": "Kies het gewenste sensor type"
@@ -553,31 +553,31 @@
},
"data_description": {
"utility_meter_net_consumption": "Schakel dit in als je de bron als netto meter wilt behandelen. Hierdoor kan je teller zowel positief als negatief zijn.",
"utility_meter_types": "Maak nutsmeters voor de opgegeven cycli",
"utility_meter_types": "Utiliteit meters voor de opgegeven cycli aanmaken",
"utility_meter_tariffs": "Een lijst met ondersteunde tarieven, laat leeg als er maar één tarief nodig is."
}
},
"virtual_power": {
"data": {
"create_energy_sensor": "Creëer energie sensoren",
"create_standby_energy_sensor": "Stand-by-energiesensor maken",
"create_cost_sensor": "Maak een kostensensor",
"create_utility_meters": "Creëer utiliteit meters",
"create_energy_sensor": "Energiesensoren aanmaken",
"create_standby_energy_sensor": "Stand-by-energiesensor aanmaken",
"create_cost_sensor": "Kostensensor aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken",
"entity_id": "Bron entiteit",
"mode": "Calculatie strategie",
"name": "Naam",
"standby_power": "Stand-by vermogen"
},
"data_description": {
"create_energy_sensor": "Laat Powercalc een kWh meter maken",
"create_standby_energy_sensor": "Of Powercalc een kWh-sensor moet maken die alleen het stand-byverbruik bijhoudt",
"create_utility_meters": "Laat powercalc utiliteit meters maken. Deze resetten dagelijks, uurlijks etc.",
"create_energy_sensor": "Laat Powercalc een kWh meter aanmaken",
"create_standby_energy_sensor": "Laat Powercalc een kWh-sensor aanmaken die alleen het stand-byverbruik bijhoudt",
"create_utility_meters": "Laat Powercalc utiliteit meters aanmaken. Deze resetten dagelijks, uurlijks etc.",
"entity_id": "entiteit waarop de virtuele energie sensor is gebaseerd, de energiesensor update zichzelf wanneer deze entiteit van status wijzigt",
"name": "Indien dit leeg gelaten wordt dan wordt de naam van de bron entiteit overgenomen",
"standby_power": "Definieer het vermogen wanneer het apparaat uit staat (OFF status in Home Assistant)"
},
"description": "Zie de readme voor meer information over de mogelijke strategieen en configuratie opties.",
"title": "Creëer een virtuele energie meter"
"title": "Virtuele energie meter aanmaken"
},
"wled": {
"data": {
@@ -610,18 +610,18 @@
"entity": {
"sensor": {
"measure_session_status": {
"name": "Measure session status",
"name": "Status van meetsessie",
"state": {
"idle": "Idle",
"validating": "Validating",
"ready": "Ready",
"awaiting_confirmation": "Awaiting confirmation",
"running": "Running",
"cancelling": "Cancelling",
"cancelled": "Cancelled",
"completed": "Completed",
"failed": "Failed",
"resumable": "Resumable"
"idle": "Inactief",
"validating": "Controleren",
"ready": "Gereed",
"awaiting_confirmation": "Wacht op bevestiging",
"running": "In uitvoering",
"cancelling": "Annuleren",
"cancelled": "Geannuleerd",
"completed": "Voltooid",
"failed": "Mislukt",
"resumable": "Hervatbaar"
}
}
}
@@ -699,27 +699,27 @@
},
"data_description": {
"calculation_enabled_condition": "De geconfigureerde vermogen calculatie strategie wordt alleen uitgevoerd indien dit template evalueert naar 1, anders zal de energiesensor 0 tonen",
"ignore_unavailable_state": "Zet dit aan wanneer je de vermogen sensor beschikbaar wil houden ongeacht de bron sensor niet beschikbaar is.",
"ignore_unavailable_state": "Zet dit aan wanneer je de vermogensensor beschikbaar wil houden ongeacht de bron sensor niet beschikbaar is",
"multiply_factor": "Vermenigvuldigd het gecalculeerde vermogen bij deze ratio. Kan handig zijn voor lamp groepen.",
"multiply_factor_standby": "Pas vermenigvuldigingsfactor toe op standby vermogen",
"unavailable_power": "Vermogen in W wanneer de bron sensor onbeschikbaar is"
"unavailable_power": "Vermogen in W wanneer de bronsensor onbeschikbaar is"
}
},
"basic_options": {
"title": "Basisopties",
"data": {
"create_energy_sensor": "Creëer energie sensoren",
"create_standby_energy_sensor": "Stand-by-energiesensor maken",
"create_cost_sensor": "Maak een kostensensor",
"create_utility_meters": "Creëer utiliteit meters",
"create_energy_sensor": "Energiesensoren aanmaken",
"create_standby_energy_sensor": "Stand-by-energiesensor aanmaken",
"create_cost_sensor": "Kostensensor aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken",
"entity_id": "Bron entiteit",
"name": "Naam",
"standby_power": "Standby vermogen"
},
"data_description": {
"create_energy_sensor": "Laat powercalc een kWh meter maken",
"create_energy_sensor": "Laat Powercalc een kWh-sensor aanmaken",
"create_standby_energy_sensor": "Of Powercalc een kWh-sensor moet maken die alleen het stand-byverbruik bijhoudt",
"create_utility_meters": "Laat powercalc utiliteit meters maken. Deze resetten dagelijks, uurlijks etc.",
"create_utility_meters": "Laat Powercalc utiliteit aanmeters maken. Deze resetten dagelijks, uurlijks etc.",
"entity_id": "entiteit waarop de virtuele energie sensor is gebaseerd, de energiesensor update zichzelf wanneer deze entiteit van status wijzigt",
"name": "Indien dit leeg gelaten wordt dan wordt de naam van de bron entiteit overgenomen",
"standby_power": "Definieer het vermogen wanneer het apparaat uit staat"
@@ -771,25 +771,25 @@
"power_options": {
"name": "Vermogenssensor",
"data": {
"power_sensor_naming": "Vermogen sensor naampatroon",
"power_sensor_friendly_naming": "Vermogen sensor vriendelijke naampatroon",
"power_sensor_category": "Vermogen sensor categorie",
"power_sensor_naming": "Vermogensensor naampatroon",
"power_sensor_friendly_naming": "Vermogensensor vriendelijke naampatroon",
"power_sensor_category": "Vermogensensor categorie",
"power_sensor_precision": "Precisie van vermogenssensor"
}
},
"features": {
"name": "Sensoren aanmaken",
"data": {
"create_energy_sensors": "Creëer energie sensoren",
"create_cost_sensors": "Maak kostensensoren",
"create_standby_group": "Maak standby-groep",
"create_utility_meters": "Creëer utiliteit meters"
"create_energy_sensors": "Energiesensoren aanmaken",
"create_cost_sensors": "Kostensensoren aanmaken",
"create_standby_group": "Standby-groep aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken"
},
"data_description": {
"create_energy_sensors": "Of powercalc kWh sensoren moet maken",
"create_cost_sensors": "Of powercalc kostensensoren moet creëren. Vereist dat in de volgende stappen een energieprijs wordt geconfigureerd",
"create_standby_group": "Maak groep met een totaal van alle stand-by en zelfgebruik van IOT apparaten",
"create_utility_meters": "Laat powercalc utiliteit meters maken. Deze resetten dagelijks, uurlijks etc."
"create_energy_sensors": "Of Powercalc kWh sensoren moet aanmaken",
"create_cost_sensors": "Of Powercalc kostensensoren moet aanmaken. Vereist dat in de volgende stappen een energieprijs wordt geconfigureerd",
"create_standby_group": "Groep met een totaal van alle stand-by en zelfgebruik van IOT apparaten aanmaken",
"create_utility_meters": "Laat Powercalc utiliteit meters aanmaken. Deze resetten dagelijks, uurlijks etc."
}
},
"advanced": {
@@ -845,7 +845,7 @@
},
"global_configuration_cost_apply": {
"title": "Pas kostensensoren toe",
"description": "U heeft de instelling voor globale kostensensoren gewijzigd. Wilt u deze wijziging toepassen op alle bestaande powercalc-sensoren die zijn gemaakt via de GUI?",
"description": "De instellingen voor globale kostensensoren zijn gewijzigd. Wilt u deze wijziging toepassen op alle bestaande Powercalc-sensoren die zijn gemaakt via de GUI?",
"data": {
"apply_to_all": "Toepassen op bestaande sensoren"
},
@@ -871,10 +871,10 @@
"description": "Definieer hier de standaardinstellingen voor energiesensoren. Zie [documentatie]({docs_uri}) voor meer informatie",
"data": {
"energy_integration_method": "Energie integratie methode",
"energy_sensor_category": "Energie sensor categorie",
"energy_sensor_friendly_naming": "Energie sensor vriendelijk naampatroon",
"energy_sensor_naming": "Energie sensor naampatroon",
"energy_sensor_precision": "Energie sensor nauwkeurigheid",
"energy_sensor_category": "Energiesensor categorie",
"energy_sensor_friendly_naming": "Energiesensor vriendelijk naampatroon",
"energy_sensor_naming": "Energiesensor naampatroon",
"energy_sensor_precision": "Energiesensor nauwkeurigheid",
"energy_sensor_unit_prefix": "Voorvoegsel van de energiesensor eenheid"
}
},
@@ -936,7 +936,7 @@
"force_calculate_group_energy": "Forceer berekenen energie"
},
"data_description": {
"device": "Voeg de entiteiten toe aan een bestaand HA apparaat",
"device": "Voeg de groepsentiteiten die Powercalc aanmaakt toe aan een bestaand apparaat",
"include_non_powercalc_sensors": "Bepaal of je niet-powercalc sensoren wilt opnemen bij gebruik van de ruimte optie",
"force_calculate_group_energy": "Standaard worden de energiesensoren van de individuele sensoren opgeteld. Schakel deze optie in om een Riemann Sum sensor te forceren"
}
@@ -958,15 +958,15 @@
"data": {
"main_power_sensor": "Hoofd vermogen sensor (P1)",
"group_tracked_auto": "Entiteiten automatisch toegevoegd",
"create_energy_sensor": "Creëer energie sensor",
"create_cost_sensor": "Maak een kostensensor",
"create_utility_meters": "Creëer utiliteit meters"
"create_energy_sensor": "Energiesensor aanmaken",
"create_cost_sensor": "Kostensensor aanmaken",
"create_utility_meters": "Utiliteit meters aanmaken"
},
"data_description": {
"main_power_sensor": "Selecteer de vermogenssensor die uw totale energieverbruik bevat. Indien leeg wordt alleen een bijgehouden groep gemaakt",
"main_power_sensor": "Selecteer de vermogenssensor die uw totale energieverbruik bevat. Indien leeg wordt alleen een bijgehouden groep aangemaakt",
"group_tracked_auto": "Powercalc voegt automatisch alle vermogen sensoren toe aan de bijgehouden groep, slaat Powercalc groepen over. Schakel deze uit als u handmatig de bijgehouden entiteiten wilt definiëren.",
"create_energy_sensor": "Laat Powercalc een kWh meter maken",
"create_utility_meters": "Laat powercalc utiliteit meters maken. Deze resetten dagelijks, uurlijks etc."
"create_energy_sensor": "Laat Powercalc een kWh meter aanmaken",
"create_utility_meters": "Laat Powercalc utiliteit meters aanmaken. Deze resetten dagelijks, uurlijks etc."
},
"title": "Bijgehouden/niet bijgehouden groep opties"
},
@@ -1060,7 +1060,7 @@
"device": "Apparaat"
},
"data_description": {
"device": "Voeg de gecreëerde energiesensor toe aan een specifiek apparaat"
"device": "Voeg de aangemaakte energiesensor toe aan een specifiek apparaat"
}
},
"cost": {