186 files
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user