58 files
This commit is contained in:
@@ -212,7 +212,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
global_config = get_global_configuration(hass, config)
|
||||
|
||||
discovery_manager = await create_discovery_manager_instance(hass, config, global_config)
|
||||
discovery_manager = create_discovery_manager_instance(hass, config, global_config)
|
||||
hass.data[DOMAIN] = {
|
||||
DATA_DISCOVERY_MANAGER: discovery_manager,
|
||||
DOMAIN_CONFIG: global_config,
|
||||
@@ -225,6 +225,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
DATA_ANALYTICS: {},
|
||||
}
|
||||
|
||||
await discovery_manager.setup()
|
||||
|
||||
register_services(hass)
|
||||
|
||||
await async_load_platform(hass, Platform.SELECT, DOMAIN, {}, config)
|
||||
@@ -235,8 +237,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
try:
|
||||
await repair_none_config_entries_issue(hass)
|
||||
except Exception as e: # pragma: no cover
|
||||
_LOGGER.error("problem while cleaning up None entities", exc_info=e) # pragma: no cover
|
||||
except Exception: # pragma: no cover
|
||||
_LOGGER.exception("problem while cleaning up None entities") # pragma: no cover
|
||||
|
||||
await init_analytics(hass)
|
||||
|
||||
@@ -272,7 +274,7 @@ async def init_analytics(hass: HomeAssistant) -> None:
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_schedule)
|
||||
|
||||
|
||||
async def create_discovery_manager_instance(
|
||||
def create_discovery_manager_instance(
|
||||
hass: HomeAssistant,
|
||||
ha_config: ConfigType,
|
||||
global_powercalc_config: ConfigType,
|
||||
@@ -284,15 +286,13 @@ async def create_discovery_manager_instance(
|
||||
exclude_self_usage = discovery_config.get(CONF_EXCLUDE_SELF_USAGE, False)
|
||||
enable_autodiscovery = discovery_config.get(CONF_ENABLED, True)
|
||||
|
||||
manager = DiscoveryManager(
|
||||
return DiscoveryManager(
|
||||
hass,
|
||||
ha_config,
|
||||
exclude_device_types=exclude_device_types,
|
||||
exclude_self_usage_profiles=exclude_self_usage,
|
||||
enabled=enable_autodiscovery,
|
||||
)
|
||||
await manager.setup()
|
||||
return manager
|
||||
|
||||
|
||||
def register_services(hass: HomeAssistant) -> None:
|
||||
@@ -579,8 +579,8 @@ async def repair_none_config_entries_issue(hass: HomeAssistant) -> None:
|
||||
object.__setattr__(entry, "unique_id", unique_id)
|
||||
hass.config_entries._entries._index_entry(entry) # noqa: SLF001
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
except Exception as e: # pragma: no cover
|
||||
_LOGGER.error("problem while cleaning up None entities", exc_info=e) # pragma: no cover
|
||||
except Exception: # pragma: no cover
|
||||
_LOGGER.exception("problem while cleaning up None entities") # pragma: no cover
|
||||
|
||||
|
||||
def _notify_message(
|
||||
|
||||
@@ -38,6 +38,7 @@ from .const import (
|
||||
CONF_POWER,
|
||||
CONF_SENSOR_TYPE,
|
||||
CONF_STANDBY_POWER,
|
||||
DISCOVERY_INTEGRATION_NAME,
|
||||
DISCOVERY_POWER_PROFILES,
|
||||
DISCOVERY_SOURCE_ENTITY,
|
||||
DOMAIN,
|
||||
@@ -400,6 +401,7 @@ class PowercalcConfigFlow(PowercalcCommonFlow, ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self.source_entity_id = self.source_entity.entity_id
|
||||
self.name = self.source_entity.name
|
||||
integration_name = discovery_info.pop(DISCOVERY_INTEGRATION_NAME, None)
|
||||
|
||||
power_profiles: list[PowerProfile] = []
|
||||
if DISCOVERY_POWER_PROFILES in discovery_info:
|
||||
@@ -410,7 +412,7 @@ class PowercalcConfigFlow(PowercalcCommonFlow, ConfigFlow, domain=DOMAIN):
|
||||
self.sensor_config = discovery_info.copy()
|
||||
|
||||
self.context["title_placeholders"] = {
|
||||
"name": self.name or "",
|
||||
"name": f"{self.name} - {integration_name}" if integration_name else self.name or "",
|
||||
"manufacturer": str(self.sensor_config.get(CONF_MANUFACTURER)),
|
||||
"model": str(self.sensor_config.get(CONF_MODEL)),
|
||||
}
|
||||
@@ -549,12 +551,7 @@ class PowercalcOptionsFlow(PowercalcCommonFlow, OptionsFlow):
|
||||
|
||||
menu = [Step.BASIC_OPTIONS]
|
||||
if self.selected_sensor_type == SensorType.VIRTUAL_POWER:
|
||||
if self.strategy and self.should_add_strategy_option_to_menu():
|
||||
strategy_step = STRATEGY_STEP_MAPPING[self.strategy]
|
||||
menu.append(strategy_step)
|
||||
if self.selected_profile:
|
||||
menu.append(Step.LIBRARY_OPTIONS)
|
||||
menu.append(Step.ADVANCED_OPTIONS)
|
||||
menu.extend(self.build_virtual_power_menu())
|
||||
if self.selected_sensor_type == SensorType.DAILY_ENERGY:
|
||||
menu.append(Step.DAILY_ENERGY)
|
||||
if self.selected_sensor_type == SensorType.REAL_POWER:
|
||||
@@ -570,6 +567,32 @@ class PowercalcOptionsFlow(PowercalcCommonFlow, OptionsFlow):
|
||||
|
||||
return menu
|
||||
|
||||
def build_virtual_power_menu(self) -> list[Step]:
|
||||
"""Build the options menu entries specific to virtual power sensors."""
|
||||
menu: list[Step] = []
|
||||
if self.strategy and self.should_add_strategy_option_to_menu():
|
||||
menu.append(STRATEGY_STEP_MAPPING[self.strategy])
|
||||
if self.selected_profile:
|
||||
menu.append(Step.LIBRARY_OPTIONS)
|
||||
if self.should_add_select_device_to_menu():
|
||||
menu.append(Step.SELECT_DEVICE)
|
||||
menu.append(Step.ADVANCED_OPTIONS)
|
||||
return menu
|
||||
|
||||
def should_add_select_device_to_menu(self) -> bool:
|
||||
"""Check whether the device selection should be added to the menu."""
|
||||
if not self.selected_profile or self.selected_profile.discovery_by not in [
|
||||
DiscoveryBy.DEVICE,
|
||||
DiscoveryBy.CONFIG_ENTRY,
|
||||
]:
|
||||
return False
|
||||
|
||||
if self.selected_profile.discovery_by == DiscoveryBy.CONFIG_ENTRY:
|
||||
# Only devices of the source config entry are selectable, so there must be something to choose from.
|
||||
return len(self.flow_handlers[FlowType.LIBRARY].get_selectable_devices()) > 1
|
||||
|
||||
return True
|
||||
|
||||
def should_add_strategy_option_to_menu(self) -> bool:
|
||||
"""Check whether the strategy option should be added to the menu."""
|
||||
if not self.strategy or self.strategy not in STRATEGY_STEP_MAPPING:
|
||||
|
||||
@@ -240,7 +240,9 @@ DEFAULT_UTILITY_METER_TYPES = [DAILY, WEEKLY, MONTHLY]
|
||||
|
||||
DISCOVERY_SOURCE_ENTITY = "source_entity"
|
||||
DISCOVERY_POWER_PROFILES = "power_profiles"
|
||||
DISCOVERY_INTEGRATION_NAME = "integration_name"
|
||||
DISCOVERY_TYPE = "discovery_type"
|
||||
LIBRARY_DISCOVERY_IGNORED_DOMAINS = "discovery_ignored_domains"
|
||||
|
||||
LIBRARY_URL = "https://library.powercalc.nl"
|
||||
API_URL = "https://api.powercalc.nl"
|
||||
|
||||
@@ -31,6 +31,54 @@ def is_composite_device_id(hass: HomeAssistant, device_id: str) -> bool:
|
||||
return bool(is_composite(device_id))
|
||||
|
||||
|
||||
def get_related_device_ids(hass: HomeAssistant, device_id: str) -> set[str]:
|
||||
"""
|
||||
Return the IDs of all devices representing the same physical device, including `device_id` itself.
|
||||
Devices are related when they were split off from the same legacy composite device, or when they
|
||||
share any identifier or connection. Both happen when a single physical device is provided by more
|
||||
than one config entry.
|
||||
`device_id` may be the ID of a registered device, or of a legacy composite device.
|
||||
"""
|
||||
device_reg = device_registry.async_get(hass)
|
||||
related = {device_id}
|
||||
related.update(device.id for device in _get_composite_split_devices(device_reg, device_id))
|
||||
|
||||
device = device_reg.async_get(device_id)
|
||||
if device is None:
|
||||
return related
|
||||
|
||||
sibling_devices = _get_composite_split_devices(device_reg, getattr(device, "composite_device_id", None))
|
||||
related.update(sibling_device.id for sibling_device in sibling_devices)
|
||||
|
||||
# Only available in HA >=2026.8. On older versions devices sharing an identifier or connection
|
||||
# were merged into a single device, so there is nothing to relate.
|
||||
get_devices = getattr(device_reg, "async_get_devices", None)
|
||||
if callable(get_devices):
|
||||
related.update(
|
||||
linked_device.id
|
||||
for linked_device in get_devices(identifiers=device.identifiers, connections=device.connections)
|
||||
)
|
||||
|
||||
return related
|
||||
|
||||
|
||||
def _get_composite_split_devices(
|
||||
device_reg: device_registry.DeviceRegistry,
|
||||
composite_device_id: str | None,
|
||||
) -> list[DeviceEntry]:
|
||||
"""
|
||||
Return the devices a legacy composite device was split into.
|
||||
Returns an empty list when the ID does not identify a composite device, or when running on
|
||||
HA <2026.8, which does not split composite devices at all.
|
||||
"""
|
||||
if not composite_device_id:
|
||||
return []
|
||||
get_split_devices = getattr(device_reg, "async_get_devices_for_composite_device_id", None)
|
||||
if not callable(get_split_devices):
|
||||
return [] # pragma: no cover
|
||||
return list(get_split_devices(composite_device_id))
|
||||
|
||||
|
||||
def get_config_entry_ids(device: DeviceEntry) -> set[str]:
|
||||
"""
|
||||
Return the config entry IDs a device belongs to.
|
||||
@@ -39,7 +87,7 @@ def get_config_entry_ids(device: DeviceEntry) -> set[str]:
|
||||
"""
|
||||
if _HAS_SINGLE_CONFIG_ENTRY:
|
||||
return {device.config_entry_id}
|
||||
return set(getattr(device, "config_entries", set()))
|
||||
return set(getattr(device, "config_entries", set())) # pragma: no cover
|
||||
|
||||
|
||||
def get_first_device_for_config_entry(hass: HomeAssistant, config_entry_id: str) -> DeviceEntry | None:
|
||||
@@ -56,6 +104,18 @@ def get_devices_for_config_entry(hass: HomeAssistant, config_entry_id: str) -> l
|
||||
]
|
||||
|
||||
|
||||
def get_related_devices(hass: HomeAssistant, device_id: str) -> list[DeviceEntry]:
|
||||
"""Return all non-composite devices belonging to the same config entry as the given device."""
|
||||
device = device_registry.async_get(hass).async_get(device_id)
|
||||
if device is None:
|
||||
return []
|
||||
|
||||
devices: dict[str, DeviceEntry] = {}
|
||||
for config_entry_id in get_config_entry_ids(device):
|
||||
devices.update({related.id: related for related in get_devices_for_config_entry(hass, config_entry_id)})
|
||||
return list(devices.values())
|
||||
|
||||
|
||||
def attach_configured_device_entry(
|
||||
hass: HomeAssistant,
|
||||
sensor_config: ConfigType,
|
||||
|
||||
@@ -49,6 +49,6 @@ async def get_yaml_configuration(hass: HomeAssistant) -> ConfigType:
|
||||
try:
|
||||
yaml_config = await async_integration_yaml_config(hass, DOMAIN)
|
||||
return yaml_config.get(DOMAIN, {}) # type: ignore
|
||||
except Exception as err: # noqa: BLE001 # pragma: nocover
|
||||
_LOGGER.error("Could not retrieve YAML config: %s", err)
|
||||
except Exception: # pragma: nocover
|
||||
_LOGGER.exception("Could not retrieve YAML config")
|
||||
return {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timedelta
|
||||
from enum import StrEnum
|
||||
import logging
|
||||
@@ -8,14 +8,15 @@ from typing import Any, TypeVar
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, SOURCE_USER, ConfigEntry
|
||||
from homeassistant.const import CONF_ENTITY_ID, CONF_PLATFORM, CONF_UNIQUE_ID
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.const import CONF_DEVICE, CONF_ENTITY_ID, CONF_PLATFORM, CONF_UNIQUE_ID
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.helpers import discovery_flow
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.loader import IntegrationNotFound, async_get_integration
|
||||
|
||||
from .common import SourceEntity, create_source_entity
|
||||
from .const import (
|
||||
@@ -24,6 +25,7 @@ from .const import (
|
||||
CONF_MODEL,
|
||||
CONF_SENSORS,
|
||||
DATA_DISCOVERY_MANAGER,
|
||||
DISCOVERY_INTEGRATION_NAME,
|
||||
DISCOVERY_POWER_PROFILES,
|
||||
DISCOVERY_SOURCE_ENTITY,
|
||||
DOMAIN,
|
||||
@@ -31,7 +33,12 @@ from .const import (
|
||||
MANUFACTURER_WLED,
|
||||
CalculationStrategy,
|
||||
)
|
||||
from .device_binding import get_config_entry_ids, get_first_device_for_config_entry, is_composite_device_id
|
||||
from .device_binding import (
|
||||
get_config_entry_ids,
|
||||
get_first_device_for_config_entry,
|
||||
get_related_device_ids,
|
||||
is_composite_device_id,
|
||||
)
|
||||
from .group_include.filter import (
|
||||
CategoryFilter,
|
||||
CompositeFilter,
|
||||
@@ -180,6 +187,8 @@ class DiscoveryManager:
|
||||
continue # pragma: no cover
|
||||
|
||||
self.initialized_flows.add(entry.unique_id)
|
||||
self._initialize_configured_device(entry)
|
||||
|
||||
entity_id = entry.data.get(CONF_ENTITY_ID)
|
||||
if not entity_id or entity_id == DUMMY_ENTITY_ID:
|
||||
continue
|
||||
@@ -189,6 +198,23 @@ class DiscoveryManager:
|
||||
self.initialized_flows.add(f"pc_{entity.device_entry.id}")
|
||||
self.initialized_flows.add(entity_id)
|
||||
|
||||
def _initialize_configured_device(self, entry: ConfigEntry) -> None:
|
||||
"""Mark the device a config entry was setup for as already setup.
|
||||
|
||||
A single physical device can be represented by several device registry entries. HA >=2026.8
|
||||
splits devices belonging to multiple config entries into one device per entry, so the entry
|
||||
may hold the composite device ID, which no longer resolves to a registered device, or one of
|
||||
the split devices after the user resolved the composite device repair. Devices can also be
|
||||
registered by several integrations, in which case they share identifiers or connections.
|
||||
All of them are the device the user already configured, so none should be discovered again.
|
||||
"""
|
||||
device_id = entry.data.get(CONF_DEVICE)
|
||||
if not device_id:
|
||||
return
|
||||
|
||||
for related_device_id in get_related_device_ids(self.hass, str(device_id)):
|
||||
self.initialized_flows.add(f"pc_{related_device_id}")
|
||||
|
||||
def remove_initialized_flow(self, entry: ConfigEntry) -> None:
|
||||
"""Remove a flow from the initialized flows."""
|
||||
if entry.unique_id:
|
||||
@@ -199,17 +225,22 @@ class DiscoveryManager:
|
||||
|
||||
async def perform_discovery(
|
||||
self,
|
||||
source_provider: Callable[[], Awaitable[list[_DiscoverySourceT]]],
|
||||
source_creator: Callable[[_DiscoverySourceT], Awaitable[SourceEntity]],
|
||||
source_provider: Callable[[], list[_DiscoverySourceT]],
|
||||
source_creator: Callable[[_DiscoverySourceT], SourceEntity],
|
||||
discovery_type: DiscoveryBy,
|
||||
) -> None:
|
||||
"""Generalized discovery procedure for entities and devices."""
|
||||
for source in await source_provider():
|
||||
library = await self._get_library()
|
||||
ignored_domains = library.discovery_ignored_domains
|
||||
for source in source_provider():
|
||||
log_identifier = str(
|
||||
getattr(source, "entity_id", getattr(source, "id", getattr(source, "entry_id", "unknown"))),
|
||||
)
|
||||
try:
|
||||
source_entity = await source_creator(source)
|
||||
if self._is_domain_ignored(source, ignored_domains):
|
||||
_LOGGER.debug("%s: Integration domain is ignored, skipping discovery", log_identifier)
|
||||
continue
|
||||
source_entity = source_creator(source)
|
||||
model_info = await self.extract_model_info_from_device_info(
|
||||
source_entity.entity_entry or source_entity.device_entry,
|
||||
)
|
||||
@@ -235,13 +266,19 @@ class DiscoveryManager:
|
||||
)
|
||||
continue
|
||||
|
||||
self._init_entity_discovery(model_info, unique_id, source_entity, log_identifier, power_profiles, {})
|
||||
except Exception as err: # noqa: BLE001
|
||||
_LOGGER.error(
|
||||
"%s: Error during %s discovery: %s",
|
||||
await self._init_entity_discovery(
|
||||
model_info,
|
||||
unique_id,
|
||||
source_entity,
|
||||
log_identifier,
|
||||
power_profiles,
|
||||
{},
|
||||
)
|
||||
except Exception:
|
||||
_LOGGER.exception(
|
||||
"%s: Error during %s discovery",
|
||||
log_identifier,
|
||||
discovery_type,
|
||||
err,
|
||||
)
|
||||
|
||||
async def discover_entity(
|
||||
@@ -256,12 +293,12 @@ class DiscoveryManager:
|
||||
|
||||
return await self.find_power_profiles(model_info, source_entity, discovery_type)
|
||||
|
||||
async def create_entity_source(self, entity_entry: er.RegistryEntry) -> SourceEntity:
|
||||
def create_entity_source(self, entity_entry: er.RegistryEntry) -> SourceEntity:
|
||||
"""Create SourceEntity for an entity."""
|
||||
return create_source_entity(entity_entry.entity_id, self.hass)
|
||||
|
||||
@staticmethod
|
||||
async def create_device_source(device_entry: dr.DeviceEntry) -> SourceEntity:
|
||||
def create_device_source(device_entry: dr.DeviceEntry) -> SourceEntity:
|
||||
"""Create SourceEntity for a device."""
|
||||
return SourceEntity(
|
||||
object_id=device_entry.name_by_user or device_entry.name or "",
|
||||
@@ -271,7 +308,7 @@ class DiscoveryManager:
|
||||
device_entry=device_entry,
|
||||
)
|
||||
|
||||
async def create_config_entry_source(self, config_entry: ConfigEntry) -> SourceEntity:
|
||||
def create_config_entry_source(self, config_entry: ConfigEntry) -> SourceEntity:
|
||||
"""Create a source representing all devices belonging to a config entry."""
|
||||
device_entry = get_first_device_for_config_entry(self.hass, config_entry.entry_id)
|
||||
return SourceEntity(
|
||||
@@ -341,6 +378,23 @@ class DiscoveryManager:
|
||||
|
||||
return power_profiles
|
||||
|
||||
def _is_domain_ignored(self, source: _DiscoverySourceT, ignored_domains: set[str]) -> bool:
|
||||
"""Return whether a discovery source belongs to a globally ignored integration domain."""
|
||||
if not ignored_domains:
|
||||
return False
|
||||
|
||||
if isinstance(source, er.RegistryEntry):
|
||||
return source.platform in ignored_domains
|
||||
if isinstance(source, ConfigEntry):
|
||||
return source.domain in ignored_domains
|
||||
|
||||
config_entry_id = next(iter(source.config_entries), None)
|
||||
if config_entry_id is None: # pragma: no cover
|
||||
return False
|
||||
|
||||
config_entry = self.hass.config_entries.async_get_entry(config_entry_id)
|
||||
return config_entry is not None and config_entry.domain in ignored_domains
|
||||
|
||||
async def init_wled_flow(self, model_info: ModelInfo, source_entity: SourceEntity) -> None:
|
||||
"""Initialize the discovery flow for a WLED light."""
|
||||
if DeviceType.LIGHT in self._exclude_device_types:
|
||||
@@ -358,7 +412,7 @@ class DiscoveryManager:
|
||||
)
|
||||
return
|
||||
|
||||
self._init_entity_discovery(
|
||||
await self._init_entity_discovery(
|
||||
model_info,
|
||||
unique_id,
|
||||
source_entity,
|
||||
@@ -379,7 +433,7 @@ class DiscoveryManager:
|
||||
and not re.search("master|segment", str(entity_entry.entity_id), flags=re.IGNORECASE)
|
||||
)
|
||||
|
||||
async def get_entities(self) -> list[er.RegistryEntry]:
|
||||
def get_entities(self) -> list[er.RegistryEntry]:
|
||||
"""Get all entities from entity registry which qualifies for discovery."""
|
||||
|
||||
def _check_already_configured(entity: er.RegistryEntry) -> bool:
|
||||
@@ -409,7 +463,7 @@ class DiscoveryManager:
|
||||
)
|
||||
return get_filtered_entity_list(self.hass, NotFilter(entity_filter))
|
||||
|
||||
async def get_devices(self) -> list[dr.DeviceEntry]:
|
||||
def get_devices(self) -> list[dr.DeviceEntry]:
|
||||
"""Fetch device entries."""
|
||||
return [
|
||||
device
|
||||
@@ -417,7 +471,7 @@ class DiscoveryManager:
|
||||
if not is_composite_device_id(self.hass, device.id)
|
||||
]
|
||||
|
||||
async def get_config_entries(self) -> list[ConfigEntry]:
|
||||
def get_config_entries(self) -> list[ConfigEntry]:
|
||||
"""Fetch config entries which have at least one non-composite device."""
|
||||
config_entry_ids = {
|
||||
config_entry_id
|
||||
@@ -518,8 +572,7 @@ class DiscoveryManager:
|
||||
|
||||
return await self.get_model_information_from_device(device_entry)
|
||||
|
||||
@callback
|
||||
def _init_entity_discovery(
|
||||
async def _init_entity_discovery(
|
||||
self,
|
||||
model_info: ModelInfo,
|
||||
unique_id: str,
|
||||
@@ -532,6 +585,7 @@ class DiscoveryManager:
|
||||
|
||||
discovery_data: dict[str, Any] = {
|
||||
CONF_ENTITY_ID: source_entity.entity_id,
|
||||
DISCOVERY_INTEGRATION_NAME: await self._get_integration_name(source_entity),
|
||||
DISCOVERY_SOURCE_ENTITY: source_entity,
|
||||
CONF_UNIQUE_ID: unique_id,
|
||||
}
|
||||
@@ -564,6 +618,27 @@ class DiscoveryManager:
|
||||
data=discovery_data,
|
||||
)
|
||||
|
||||
async def _get_integration_name(self, source_entity: SourceEntity) -> str | None:
|
||||
"""Return the display name of the integration which owns the discovery source."""
|
||||
config_entry_id = source_entity.config_entry_id
|
||||
if config_entry_id is None and source_entity.entity_entry:
|
||||
config_entry_id = source_entity.entity_entry.config_entry_id
|
||||
if config_entry_id is None and source_entity.device_entry:
|
||||
config_entry_id = next(iter(get_config_entry_ids(source_entity.device_entry)), None)
|
||||
if config_entry_id is None:
|
||||
return None
|
||||
|
||||
config_entry = self.hass.config_entries.async_get_entry(config_entry_id)
|
||||
if config_entry is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
integration = await async_get_integration(self.hass, config_entry.domain)
|
||||
except IntegrationNotFound:
|
||||
_LOGGER.debug("Unable to resolve integration name for domain %s", config_entry.domain)
|
||||
return None
|
||||
return integration.name
|
||||
|
||||
@property
|
||||
def status(self) -> DiscoveryStatus:
|
||||
"""Get the discovery status"""
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any
|
||||
from homeassistant.config_entries import ConfigFlowResult
|
||||
from homeassistant.const import CONF_DEVICE
|
||||
from homeassistant.helpers import selector, translation
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
import voluptuous as vol
|
||||
|
||||
from custom_components.powercalc.const import (
|
||||
@@ -20,7 +21,7 @@ from custom_components.powercalc.const import (
|
||||
LIBRARY_URL,
|
||||
CalculationStrategy,
|
||||
)
|
||||
from custom_components.powercalc.device_binding import get_devices_for_config_entry
|
||||
from custom_components.powercalc.device_binding import get_devices_for_config_entry, get_related_devices
|
||||
from custom_components.powercalc.discovery import (
|
||||
get_power_profile_by_source_device,
|
||||
get_power_profile_by_source_entity,
|
||||
@@ -225,33 +226,51 @@ class LibraryFlow:
|
||||
|
||||
async def async_step_select_device(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
"""Ask which device should receive the entities created for a config-entry profile."""
|
||||
assert self.flow.source_entity is not None
|
||||
assert self.flow.source_entity.config_entry_id is not None
|
||||
devices = get_devices_for_config_entry(self.flow.hass, self.flow.source_entity.config_entry_id)
|
||||
return await self.flow.handle_form_step(
|
||||
PowercalcFormStep(
|
||||
step=Step.SELECT_DEVICE,
|
||||
schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_DEVICE): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=[
|
||||
selector.SelectOptionDict(
|
||||
value=device.id,
|
||||
label=device.name_by_user or device.name or device.id,
|
||||
)
|
||||
for device in devices
|
||||
],
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
schema=self.build_select_device_schema(),
|
||||
next_step=Step.POST_LIBRARY,
|
||||
),
|
||||
user_input,
|
||||
)
|
||||
|
||||
def build_select_device_schema(self) -> vol.Schema:
|
||||
"""Build the schema to select the device the Powercalc entities should be linked to."""
|
||||
discovery_by = self.flow.selected_profile.discovery_by if self.flow.selected_profile else DiscoveryBy.ENTITY
|
||||
|
||||
if discovery_by == DiscoveryBy.CONFIG_ENTRY:
|
||||
# The entities can only be linked to one of the devices of the source config entry.
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_DEVICE): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=[
|
||||
selector.SelectOptionDict(
|
||||
value=device.id,
|
||||
label=device.name_by_user or device.name or device.id,
|
||||
)
|
||||
for device in self.get_selectable_devices()
|
||||
],
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
return vol.Schema({vol.Required(CONF_DEVICE): selector.DeviceSelector()})
|
||||
|
||||
def get_selectable_devices(self) -> list[DeviceEntry]:
|
||||
"""Return the devices belonging to the same config entry as the source of the profile."""
|
||||
config_entry_id = self.flow.source_entity.config_entry_id if self.flow.source_entity else None
|
||||
if config_entry_id:
|
||||
return get_devices_for_config_entry(self.flow.hass, config_entry_id)
|
||||
|
||||
# The options flow has no reference to the source config entry anymore,
|
||||
# so resolve the candidates from the currently configured device instead.
|
||||
device_id: str | None = self.flow.sensor_config.get(CONF_DEVICE)
|
||||
return get_related_devices(self.flow.hass, device_id) if device_id else []
|
||||
|
||||
async def _async_next_strategy_step(self, profile: PowerProfile) -> ConfigFlowResult | None:
|
||||
"""Return the next step needed to configure the calculation strategy, or None when nothing is left to ask."""
|
||||
handled_steps = self.flow.handled_steps
|
||||
@@ -641,6 +660,14 @@ class LibraryOptionsFlow(LibraryFlow):
|
||||
super().__init__(flow)
|
||||
self.flow: PowercalcOptionsFlow = flow
|
||||
|
||||
async def async_step_select_device(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
"""Change the device the Powercalc entities are linked to."""
|
||||
return await self.flow.async_handle_options_step(
|
||||
user_input,
|
||||
self.build_select_device_schema(),
|
||||
Step.SELECT_DEVICE,
|
||||
)
|
||||
|
||||
async def async_step_library_options(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
|
||||
"""Handle the basic options flow."""
|
||||
self.flow.is_library_flow = True
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
"requirements": [
|
||||
"numpy>=1.21.1"
|
||||
],
|
||||
"version": "v1.24.0"
|
||||
"version": "v1.24.1"
|
||||
}
|
||||
@@ -52,6 +52,11 @@ class ProfileLibrary:
|
||||
async def initialize(self) -> None:
|
||||
await self._loader.initialize()
|
||||
|
||||
@property
|
||||
def discovery_ignored_domains(self) -> set[str]:
|
||||
"""Get integration domains globally excluded from discovery."""
|
||||
return self._loader.get_discovery_ignored_domains()
|
||||
|
||||
@staticmethod
|
||||
@singleton("powercalc_library")
|
||||
async def factory(hass: HomeAssistant) -> ProfileLibrary:
|
||||
|
||||
@@ -15,6 +15,10 @@ class CompositeLoader(Loader):
|
||||
for loader in self.loaders:
|
||||
await loader.initialize()
|
||||
|
||||
def get_discovery_ignored_domains(self) -> set[str]:
|
||||
"""Get all integration domains excluded by the combined libraries."""
|
||||
return {domain for loader in self.loaders for domain in loader.get_discovery_ignored_domains()}
|
||||
|
||||
async def get_manufacturer_listing(
|
||||
self,
|
||||
device_types: set[DeviceType] | None,
|
||||
|
||||
@@ -25,6 +25,10 @@ class LocalLoader(Loader):
|
||||
if not self._is_custom_directory:
|
||||
await self._hass.async_add_executor_job(self._load_custom_library)
|
||||
|
||||
def get_discovery_ignored_domains(self) -> set[str]:
|
||||
"""Local profile directories do not provide global library metadata."""
|
||||
return set()
|
||||
|
||||
async def get_manufacturer_listing(
|
||||
self,
|
||||
device_types: set[DeviceType] | None,
|
||||
|
||||
@@ -7,6 +7,9 @@ class Loader(Protocol):
|
||||
async def initialize(self) -> None:
|
||||
"""Initialize the loader."""
|
||||
|
||||
def get_discovery_ignored_domains(self) -> set[str]:
|
||||
"""Get integration domains excluded from discovery."""
|
||||
|
||||
async def get_manufacturer_listing(
|
||||
self,
|
||||
device_types: set[DeviceType] | None,
|
||||
|
||||
@@ -16,7 +16,12 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.storage import STORAGE_DIR
|
||||
from homeassistant.loader import async_get_integration
|
||||
|
||||
from custom_components.powercalc.const import API_URL, BUILT_IN_LIBRARY_DIR, DOMAIN
|
||||
from custom_components.powercalc.const import (
|
||||
API_URL,
|
||||
BUILT_IN_LIBRARY_DIR,
|
||||
DOMAIN,
|
||||
LIBRARY_DISCOVERY_IGNORED_DOMAINS,
|
||||
)
|
||||
from custom_components.powercalc.helpers import async_cache, clear_async_cache
|
||||
from custom_components.powercalc.power_profile.error import LibraryLoadingError, ProfileDownloadError
|
||||
from custom_components.powercalc.power_profile.loader.protocol import Loader
|
||||
@@ -80,6 +85,10 @@ class RemoteLoader(Loader):
|
||||
for manufacturer in manufacturers:
|
||||
self._index_manufacturer(manufacturer, powercalc_version)
|
||||
|
||||
def get_discovery_ignored_domains(self) -> set[str]:
|
||||
"""Get integration domains excluded from discovery by library metadata."""
|
||||
return set(self.library_contents.get(LIBRARY_DISCOVERY_IGNORED_DOMAINS, []))
|
||||
|
||||
def _index_manufacturer(self, manufacturer: LibraryManufacturer, powercalc_version: AwesomeVersion) -> None:
|
||||
"""Register a manufacturer, its aliases and all of its supported models in the lookup tables."""
|
||||
manufacturer_name = str(manufacturer.get("dir_name"))
|
||||
|
||||
@@ -538,11 +538,8 @@ async def handle_nested_entity(
|
||||
),
|
||||
)
|
||||
entities_to_add.extend_items(child_entities)
|
||||
except SensorConfigurationError as exception:
|
||||
_LOGGER.error(
|
||||
"Group state might be misbehaving because there was an error with an entity",
|
||||
exc_info=exception,
|
||||
)
|
||||
except SensorConfigurationError:
|
||||
_LOGGER.exception("Group state might be misbehaving because there was an error with an entity")
|
||||
|
||||
|
||||
async def add_discovered_entities(
|
||||
|
||||
@@ -938,8 +938,8 @@ class PreviousStateStore:
|
||||
instance.states[group] = {
|
||||
entity_id: State.from_dict(json_state) for (entity_id, json_state) in entities.items()
|
||||
}
|
||||
except HomeAssistantError as exc: # pragma: no cover
|
||||
_LOGGER.error("Error loading previous energy sensor states", exc_info=exc)
|
||||
except HomeAssistantError: # pragma: no cover
|
||||
_LOGGER.exception("Error loading previous energy sensor states")
|
||||
|
||||
instance.async_setup_dump()
|
||||
|
||||
@@ -983,8 +983,8 @@ class PreviousStateStore:
|
||||
"""Save the current states to storage."""
|
||||
try:
|
||||
await self.store.async_save(self.states)
|
||||
except HomeAssistantError as exc: # pragma: no cover
|
||||
_LOGGER.error("Error saving current states", exc_info=exc)
|
||||
except HomeAssistantError: # pragma: no cover
|
||||
_LOGGER.exception("Error saving current states")
|
||||
|
||||
@callback
|
||||
def async_setup_dump(self) -> None:
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Možnosti playbooku",
|
||||
"multi_switch": "Možnosti vícenásobného spínače",
|
||||
"real_power": "Možnosti skutečného výkonu",
|
||||
"select_device": "Zařízení",
|
||||
"utility_meter_options": "Možnosti měřičů spotřeby",
|
||||
"wled": "Možnosti WLED",
|
||||
"cost_options": "Možnosti nákladů"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Možnosti knihovny",
|
||||
"description": "Aktuálně je vybrán následující profil knihovny: \n výrobce: {manufacturer}\n model: {model}\n\nPokud chcete profil změnit, klikněte na Další."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Zařízení"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Zařízení, ke kterému mají být entity Powercalc připojeny"
|
||||
},
|
||||
"description": "Vyberte zařízení, ke kterému má být senzor Powercalc připojen.",
|
||||
"title": "Vyberte zařízení"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Lineární možnosti",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbook-indstillinger",
|
||||
"multi_switch": "Indstillinger for multikontakt",
|
||||
"real_power": "Indstillinger for fysisk effekt",
|
||||
"select_device": "Enhed",
|
||||
"utility_meter_options": "Indstillinger for forbrugsmålere",
|
||||
"wled": "WLED-indstillinger",
|
||||
"cost_options": "Omkostningsindstillinger"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Biblioteksindstillinger",
|
||||
"description": "I øjeblikket er følgende biblioteksprofil valgt: \n producent: {manufacturer}\n model: {model}\n\nHvis du vil ændre profilen, skal du klikke på næste."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Enhed"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Den enhed, som Powercalc-entiteterne skal knyttes til"
|
||||
},
|
||||
"description": "Vælg den enhed, som Powercalc-sensoren skal knyttes til.",
|
||||
"title": "Vælg enhed"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Lineære indstillinger",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbook-Optionen",
|
||||
"multi_switch": "Mehrfachschalter-Optionen",
|
||||
"real_power": "Optionen für reale Leistung",
|
||||
"select_device": "Gerät",
|
||||
"utility_meter_options": "Verbrauchszähler-Optionen",
|
||||
"wled": "WLED Einstellung",
|
||||
"cost_options": "Kostenoptionen"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Bibliotheksoptionen",
|
||||
"description": "Derzeit ist folgendes Bibliotheksprofil ausgewählt: \n Hersteller: {manufacturer}\n Modell: {model}\n\nWenn du das Profil ändern möchtest, klicke auf Weiter."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Gerät"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Das Gerät, mit dem die Powercalc-Entitäten verknüpft werden sollen"
|
||||
},
|
||||
"description": "Wählen Sie das Gerät aus, mit dem der Powercalc-Sensor verknüpft werden soll.",
|
||||
"title": "Gerät auswählen"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Lineare Optionen",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbook options",
|
||||
"multi_switch": "Multi switch options",
|
||||
"real_power": "Real power options",
|
||||
"select_device": "Device",
|
||||
"utility_meter_options": "Utility meter options",
|
||||
"wled": "WLED options",
|
||||
"cost_options": "Cost options"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Library options",
|
||||
"description": "Currently the following library profile is selected: \n manufacturer: {manufacturer}\n model: {model}\n\nIf you want to change the profile, click next."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Device"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "The device the Powercalc entities should be linked to"
|
||||
},
|
||||
"description": "Select the device to which the Powercalc sensor should be linked.",
|
||||
"title": "Select device"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Linear options",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Opciones de Reproducciones",
|
||||
"multi_switch": "Opciones multi-interruptor",
|
||||
"real_power": "Opciones de potencia real",
|
||||
"select_device": "Dispositivo",
|
||||
"utility_meter_options": "Opciones de contador eléctrico",
|
||||
"wled": "Opciones de WLED",
|
||||
"cost_options": "Opciones de costos"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Opciones de biblioteca",
|
||||
"description": "Actualmente se ha seleccionado el siguiente perfil de biblioteca: \n fabricante: {manufacturer}\n modelo: {model}\n\nSi quieres cambiar el perfil, haz clic a continuación."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Dispositivo"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "El dispositivo al que se deben vincular las entidades de Powercalc"
|
||||
},
|
||||
"description": "Seleccione el dispositivo al que se debe vincular el sensor de Powercalc.",
|
||||
"title": "Seleccionar dispositivo"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Opciones lineales",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbookin vaihtoehdot",
|
||||
"multi_switch": "Useita kytkinvaihtoehtoja",
|
||||
"real_power": "Todelliset tehovaihtoehdot",
|
||||
"select_device": "Laite",
|
||||
"utility_meter_options": "Kulutusmittarin asetukset",
|
||||
"wled": "WLED vaihtoehdot",
|
||||
"cost_options": "Kustannusvaihtoehdot"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Kirjaston vaihtoehdot",
|
||||
"description": "Tällä hetkellä on valittuna seuraava kirjastoprofiili: \n valmistaja: {manufacturer}\n malli: {model}\n\nJos haluat vaihtaa profiilia, napsauta Seuraava."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Laite"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Laite, johon Powercalc-entiteetit liitetään"
|
||||
},
|
||||
"description": "Valitse laite, johon Powercalc-anturi liitetään.",
|
||||
"title": "Valitse laite"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Lineaariset vaihtoehdot",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Options du playbook",
|
||||
"multi_switch": "Options du multi-interrupteur",
|
||||
"real_power": "Options de puissance réelle",
|
||||
"select_device": "Appareil",
|
||||
"utility_meter_options": "Options des compteurs de services publics",
|
||||
"wled": "Options WLED",
|
||||
"cost_options": "Options de coût"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Options de bibliothèque",
|
||||
"description": "Le profil de bibliothèque suivant est actuellement sélectionné : \n fabricant : {manufacturer}\n modèle : {model}\n\nSi vous souhaitez changer de profil, cliquez sur Suivant."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Appareil"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "L’appareil auquel les entités Powercalc doivent être associées"
|
||||
},
|
||||
"description": "Sélectionnez l’appareil auquel le capteur Powercalc doit être associé.",
|
||||
"title": "Sélectionner un appareil"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Options linéaires",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbook beállításai",
|
||||
"multi_switch": "Többkapcsolós beállítások",
|
||||
"real_power": "Valós teljesítmény beállításai",
|
||||
"select_device": "Eszköz",
|
||||
"utility_meter_options": "Közüzemi mérő beállításai",
|
||||
"wled": "WLED beállításai",
|
||||
"cost_options": "Költségbeállítások"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Könyvtári beállítások",
|
||||
"description": "Jelenleg a következő könyvtári profil van kiválasztva: \n gyártó: {manufacturer}\n modell: {model}\n\nHa módosítani szeretné a profilt, kattintson a Tovább gombra."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Eszköz"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Az eszköz, amelyhez a Powercalc-entitásokat hozzá kell rendelni"
|
||||
},
|
||||
"description": "Válassza ki azt az eszközt, amelyhez a Powercalc-érzékelőt hozzá kell rendelni.",
|
||||
"title": "Eszköz kiválasztása"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Lineáris beállítások",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Opzioni del playbook",
|
||||
"multi_switch": "Opzioni multiinterruttore",
|
||||
"real_power": "Opzioni di potenza reale",
|
||||
"select_device": "Dispositivo",
|
||||
"utility_meter_options": "Opzioni del contatore di utilità",
|
||||
"wled": "Opzioni WLED",
|
||||
"cost_options": "Opzioni di costo"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Opzioni della libreria",
|
||||
"description": "Attualmente è selezionato il seguente profilo libreria: \n produttore: {manufacturer}\n modello: {model}\n\nSe desideri modificare il profilo, fai clic su Avanti."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Dispositivo"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Il dispositivo a cui devono essere collegate le entità Powercalc"
|
||||
},
|
||||
"description": "Seleziona il dispositivo a cui deve essere collegato il sensore Powercalc.",
|
||||
"title": "Seleziona dispositivo"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Opzioni lineari",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbook-alternativer",
|
||||
"multi_switch": "Alternativer for flere brytere",
|
||||
"real_power": "Innstillinger for målt effekt",
|
||||
"select_device": "Enhet",
|
||||
"utility_meter_options": "Innstillinger for forbruksmåler",
|
||||
"wled": "WLED-innstillinger",
|
||||
"cost_options": "Kostnadsalternativer"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Alternativer for bibliotek",
|
||||
"description": "For øyeblikket er følgende bibliotekprofil valgt: \n produsent: {manufacturer}\n modell: {model}\n\nHvis du vil endre profilen, klikker du på neste."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Enhet"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Enheten Powercalc-entitetene skal knyttes til"
|
||||
},
|
||||
"description": "Velg enheten som Powercalc-sensoren skal knyttes til.",
|
||||
"title": "Velg enhet"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Lineære alternativer",
|
||||
"data": {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"already_configured": "Sensor is reeds geconfigureerd, specifieer een uniek ID"
|
||||
},
|
||||
"error": {
|
||||
"cost_price_mandatory": "U moet een energieprijs of een energieprijssensor aanleveren",
|
||||
"cost_price_mandatory": "U moet een energieprijs of een sensor van de energieprijs leveren",
|
||||
"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",
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbook opties",
|
||||
"multi_switch": "Multi switch opties",
|
||||
"real_power": "Opties voor werkelijk vermogen",
|
||||
"select_device": "Apparaat",
|
||||
"utility_meter_options": "Nutsmeter opties",
|
||||
"wled": "WLED opties",
|
||||
"cost_options": "Kosten opties"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Bibliotheek opties",
|
||||
"description": "Momenteel is het huidige profiel geselecteerd: \nfabrikant: {manufacturer}\nmodel: {model}\n\nAls u het profiel wilt wijzigen, klikt u op volgende."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Apparaat"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Het apparaat waaraan de Powercalc-entiteiten moeten worden gekoppeld"
|
||||
},
|
||||
"description": "Selecteer het apparaat waaraan de Powercalc-sensor moet worden gekoppeld.",
|
||||
"title": "Apparaat selecteren"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Lineaire opties",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Opcje playbook",
|
||||
"multi_switch": "Opcje wieloprzełącznika",
|
||||
"real_power": "Opcje rzeczywistej mocy",
|
||||
"select_device": "Urządzenie",
|
||||
"utility_meter_options": "Opcje licznika mediów",
|
||||
"wled": "Opcje WLED",
|
||||
"cost_options": "Opcje kosztów"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Opcje biblioteki",
|
||||
"description": "Obecnie wybrany jest następujący profil biblioteki: \n producent: {manufacturer}\n model: {model}\n\nJeśli chcesz zmienić profil, kliknij dalej."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Urządzenie"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Urządzenie, z którym mają zostać powiązane encje Powercalc"
|
||||
},
|
||||
"description": "Wybierz urządzenie, z którym ma zostać powiązany czujnik Powercalc.",
|
||||
"title": "Wybierz urządzenie"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Opcje liniowe",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Opções de roteiro",
|
||||
"multi_switch": "Opções de multi-interruptor",
|
||||
"real_power": "Opções de potência real",
|
||||
"select_device": "Dispositivo",
|
||||
"utility_meter_options": "Opções do medidor de utilidade",
|
||||
"wled": "Opções de WLED",
|
||||
"cost_options": "Opções de custo"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Opções da biblioteca",
|
||||
"description": "Atualmente o seguinte perfil de biblioteca esta selecionado: \n fabricante {manufacturer}\n modelo: {model}\n\nSe você deseja alterar o perfil, clique em próximo."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Dispositivo"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "O dispositivo ao qual as entidades do Powercalc devem ser vinculadas"
|
||||
},
|
||||
"description": "Selecione o dispositivo ao qual o sensor do Powercalc deve ser vinculado.",
|
||||
"title": "Selecionar dispositivo"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Opções linear",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Opções de sequência",
|
||||
"multi_switch": "Opções de interruptor múltiplo",
|
||||
"real_power": "Opções de potência real",
|
||||
"select_device": "Dispositivo",
|
||||
"utility_meter_options": "Opções de contador de serviços",
|
||||
"wled": "Opções WLED",
|
||||
"cost_options": "Opções de custo"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Opções da biblioteca",
|
||||
"description": "Atualmente está selecionado o seguinte perfil da biblioteca: \n fabricante: {manufacturer}\n modelo: {model}\n\nSe quiser alterar o perfil, clique em seguinte."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Dispositivo"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "O dispositivo ao qual as entidades do Powercalc devem ser associadas"
|
||||
},
|
||||
"description": "Selecione o dispositivo ao qual o sensor do Powercalc deve ser associado.",
|
||||
"title": "Selecionar dispositivo"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Opções lineares",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Opțiuni de playbook",
|
||||
"multi_switch": "Opțiuni de comutare multiplă",
|
||||
"real_power": "Opțiuni de putere reală",
|
||||
"select_device": "Dispozitiv",
|
||||
"utility_meter_options": "Opțiuni de contor de utilitate",
|
||||
"wled": "Opțiuni WLED",
|
||||
"cost_options": "Opțiuni de cost"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Opțiuni de bibliotecă",
|
||||
"description": "În prezent este selectat următorul profil de bibliotecă: \n producator: {manufacturer}\n model: {model}\n\nDacă doriți să schimbați profilul, faceți clic pe următorul."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Dispozitiv"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Dispozitivul la care trebuie asociate entitățile Powercalc"
|
||||
},
|
||||
"description": "Selectați dispozitivul la care trebuie asociat senzorul Powercalc.",
|
||||
"title": "Selectați dispozitivul"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Opțiuni liniare",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Параметры сценариев",
|
||||
"multi_switch": "Параметры мультивыключателя",
|
||||
"real_power": "Параметры реальной мощности",
|
||||
"select_device": "Устройство",
|
||||
"utility_meter_options": "Параметры счётчиков",
|
||||
"wled": "Параметры WLED",
|
||||
"cost_options": "Варианты стоимости"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Параметры библиотеки",
|
||||
"description": "В настоящее время выбран следующий профиль библиотеки: \n производитель: {manufacturer}\n модель: {model}\n\nЕсли хотите изменить профиль, нажмите далее."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Устройство"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Устройство, к которому следует привязать сущности Powercalc"
|
||||
},
|
||||
"description": "Выберите устройство, к которому следует привязать датчик Powercalc.",
|
||||
"title": "Выберите устройство"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Линейные параметры",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Možnosti playbooku",
|
||||
"multi_switch": "Možnosti viacerých prepínačov",
|
||||
"real_power": "Skutočné možnosti napájania",
|
||||
"select_device": "Zariadenie",
|
||||
"utility_meter_options": "Možnosti elektromera",
|
||||
"wled": "WLED možnosti",
|
||||
"cost_options": "Možnosti nákladov"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Možnosti knižnice",
|
||||
"description": "Momentálne je vybratý nasledujúci profil knižnice: \n výrobca: {manufacturer}\n model: {model}\n\nAk chcete profil zmeniť, kliknite na tlačidlo Ďalej."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Zariadenie"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Zariadenie, ku ktorému majú byť priradené entity Powercalc"
|
||||
},
|
||||
"description": "Vyberte zariadenie, ku ktorému má byť priradený senzor Powercalc.",
|
||||
"title": "Vyberte zariadenie"
|
||||
},
|
||||
"linear": {
|
||||
"title": "lineárne možnosti",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "Playbook-alternativ",
|
||||
"multi_switch": "Alternativ för flera switchar",
|
||||
"real_power": "Verkliga kraftalternativ",
|
||||
"select_device": "Enhet",
|
||||
"utility_meter_options": "Alternativ för verktygsmätare",
|
||||
"wled": "WLED alternativ",
|
||||
"cost_options": "Kostnadsalternativ"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "Biblioteksalternativ",
|
||||
"description": "För närvarande är följande biblioteksprofil vald: \n tillverkare: {manufacturer}\n modell: {model}\n\nOm du vill ändra profilen klickar du på nästa."
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "Enhet"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Enheten som Powercalc-entiteterna ska länkas till"
|
||||
},
|
||||
"description": "Välj enheten som Powercalc-sensorn ska länkas till.",
|
||||
"title": "Välj enhet"
|
||||
},
|
||||
"linear": {
|
||||
"title": "Linjära alternativ",
|
||||
"data": {
|
||||
|
||||
@@ -971,6 +971,7 @@
|
||||
"playbook": "运行方案选项",
|
||||
"multi_switch": "多路开关选项",
|
||||
"real_power": "实际功率选项",
|
||||
"select_device": "设备",
|
||||
"utility_meter_options": "公用事业计量表选项",
|
||||
"wled": "WLED 选项",
|
||||
"cost_options": "费用选项"
|
||||
@@ -980,6 +981,16 @@
|
||||
"title": "配置文件库选项",
|
||||
"description": "当前选择了以下配置文件库中的配置文件:\n 制造商:{manufacturer}\n 型号:{model}\n\n如需更改配置文件,请点击“下一步”。"
|
||||
},
|
||||
"select_device": {
|
||||
"data": {
|
||||
"device": "设备"
|
||||
},
|
||||
"data_description": {
|
||||
"device": "Powercalc 实体应关联到的设备"
|
||||
},
|
||||
"description": "选择 Powercalc 传感器应关联到的设备。",
|
||||
"title": "选择设备"
|
||||
},
|
||||
"linear": {
|
||||
"title": "线性选项",
|
||||
"data": {
|
||||
|
||||
Reference in New Issue
Block a user