mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Add config flow to compensation helper
This commit is contained in:
parent
48978fb7f6
commit
d73e12df93
@ -7,15 +7,18 @@ import numpy as np
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ATTRIBUTE,
|
CONF_ATTRIBUTE,
|
||||||
CONF_MAXIMUM,
|
CONF_MAXIMUM,
|
||||||
CONF_MINIMUM,
|
CONF_MINIMUM,
|
||||||
|
CONF_NAME,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryError
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.discovery import async_load_platform
|
from homeassistant.helpers.discovery import async_load_platform
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
@ -32,6 +35,7 @@ from .const import (
|
|||||||
DEFAULT_DEGREE,
|
DEFAULT_DEGREE,
|
||||||
DEFAULT_PRECISION,
|
DEFAULT_PRECISION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
PLATFORMS,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -77,59 +81,96 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def create_compensation_data(
|
||||||
|
hass: HomeAssistant, compensation: str, conf: ConfigType, should_raise: bool = False
|
||||||
|
) -> None:
|
||||||
|
"""Create compensation data."""
|
||||||
|
_LOGGER.debug("Setup %s.%s", DOMAIN, compensation)
|
||||||
|
|
||||||
|
degree = conf[CONF_DEGREE]
|
||||||
|
|
||||||
|
initial_coefficients: list[tuple[float, float]] = conf[CONF_DATAPOINTS]
|
||||||
|
sorted_coefficients = sorted(initial_coefficients, key=itemgetter(0))
|
||||||
|
|
||||||
|
# get x values and y values from the x,y point pairs
|
||||||
|
x_values, y_values = zip(*initial_coefficients, strict=False)
|
||||||
|
|
||||||
|
# try to get valid coefficients for a polynomial
|
||||||
|
coefficients = None
|
||||||
|
with np.errstate(all="raise"):
|
||||||
|
try:
|
||||||
|
coefficients = np.polyfit(x_values, y_values, degree)
|
||||||
|
except FloatingPointError as error:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Setup of %s encountered an error, %s",
|
||||||
|
compensation,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
if should_raise:
|
||||||
|
raise ConfigEntryError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="setup_error",
|
||||||
|
translation_placeholders={
|
||||||
|
"title": conf[CONF_NAME],
|
||||||
|
"error": str(error),
|
||||||
|
},
|
||||||
|
) from error
|
||||||
|
|
||||||
|
if coefficients is not None:
|
||||||
|
data = {
|
||||||
|
k: v for k, v in conf.items() if k not in [CONF_DEGREE, CONF_DATAPOINTS]
|
||||||
|
}
|
||||||
|
data[CONF_POLYNOMIAL] = np.poly1d(coefficients)
|
||||||
|
|
||||||
|
if data[CONF_LOWER_LIMIT]:
|
||||||
|
data[CONF_MINIMUM] = sorted_coefficients[0]
|
||||||
|
else:
|
||||||
|
data[CONF_MINIMUM] = None
|
||||||
|
|
||||||
|
if data[CONF_UPPER_LIMIT]:
|
||||||
|
data[CONF_MAXIMUM] = sorted_coefficients[-1]
|
||||||
|
else:
|
||||||
|
data[CONF_MAXIMUM] = None
|
||||||
|
|
||||||
|
hass.data[DATA_COMPENSATION][compensation] = data
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the Compensation sensor."""
|
"""Set up the Compensation sensor."""
|
||||||
hass.data[DATA_COMPENSATION] = {}
|
hass.data[DATA_COMPENSATION] = {}
|
||||||
|
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
for compensation, conf in config[DOMAIN].items():
|
for compensation, conf in config[DOMAIN].items():
|
||||||
_LOGGER.debug("Setup %s.%s", DOMAIN, compensation)
|
await create_compensation_data(hass, compensation, conf)
|
||||||
|
hass.async_create_task(
|
||||||
degree = conf[CONF_DEGREE]
|
async_load_platform(
|
||||||
|
hass,
|
||||||
initial_coefficients: list[tuple[float, float]] = conf[CONF_DATAPOINTS]
|
SENSOR_DOMAIN,
|
||||||
sorted_coefficients = sorted(initial_coefficients, key=itemgetter(0))
|
DOMAIN,
|
||||||
|
{CONF_COMPENSATION: compensation},
|
||||||
# get x values and y values from the x,y point pairs
|
config,
|
||||||
x_values, y_values = zip(*initial_coefficients, strict=False)
|
|
||||||
|
|
||||||
# try to get valid coefficients for a polynomial
|
|
||||||
coefficients = None
|
|
||||||
with np.errstate(all="raise"):
|
|
||||||
try:
|
|
||||||
coefficients = np.polyfit(x_values, y_values, degree)
|
|
||||||
except FloatingPointError as error:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Setup of %s encountered an error, %s",
|
|
||||||
compensation,
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
|
|
||||||
if coefficients is not None:
|
|
||||||
data = {
|
|
||||||
k: v for k, v in conf.items() if k not in [CONF_DEGREE, CONF_DATAPOINTS]
|
|
||||||
}
|
|
||||||
data[CONF_POLYNOMIAL] = np.poly1d(coefficients)
|
|
||||||
|
|
||||||
if data[CONF_LOWER_LIMIT]:
|
|
||||||
data[CONF_MINIMUM] = sorted_coefficients[0]
|
|
||||||
else:
|
|
||||||
data[CONF_MINIMUM] = None
|
|
||||||
|
|
||||||
if data[CONF_UPPER_LIMIT]:
|
|
||||||
data[CONF_MAXIMUM] = sorted_coefficients[-1]
|
|
||||||
else:
|
|
||||||
data[CONF_MAXIMUM] = None
|
|
||||||
|
|
||||||
hass.data[DATA_COMPENSATION][compensation] = data
|
|
||||||
|
|
||||||
hass.async_create_task(
|
|
||||||
async_load_platform(
|
|
||||||
hass,
|
|
||||||
SENSOR_DOMAIN,
|
|
||||||
DOMAIN,
|
|
||||||
{CONF_COMPENSATION: compensation},
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Compensation from a config entry."""
|
||||||
|
await create_compensation_data(hass, entry.entry_id, dict(entry.options), True)
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload Compensation config entry."""
|
||||||
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Handle options update."""
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
132
homeassistant/components/compensation/config_flow.py
Normal file
132
homeassistant/components/compensation/config_flow.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
"""Config flow for statistics."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ATTRIBUTE,
|
||||||
|
CONF_ENTITY_ID,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.schema_config_entry_flow import (
|
||||||
|
SchemaCommonFlowHandler,
|
||||||
|
SchemaConfigFlowHandler,
|
||||||
|
SchemaFlowError,
|
||||||
|
SchemaFlowFormStep,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.selector import (
|
||||||
|
AttributeSelector,
|
||||||
|
AttributeSelectorConfig,
|
||||||
|
BooleanSelector,
|
||||||
|
EntitySelector,
|
||||||
|
NumberSelector,
|
||||||
|
NumberSelectorConfig,
|
||||||
|
NumberSelectorMode,
|
||||||
|
SelectSelector,
|
||||||
|
SelectSelectorConfig,
|
||||||
|
SelectSelectorMode,
|
||||||
|
TextSelector,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_DATAPOINTS,
|
||||||
|
CONF_DEGREE,
|
||||||
|
CONF_LOWER_LIMIT,
|
||||||
|
CONF_PRECISION,
|
||||||
|
CONF_UPPER_LIMIT,
|
||||||
|
DEFAULT_DEGREE,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DEFAULT_PRECISION,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_options_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
|
||||||
|
"""Get options schema."""
|
||||||
|
entity_id = handler.options[CONF_ENTITY_ID]
|
||||||
|
|
||||||
|
return vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_DATAPOINTS): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=[],
|
||||||
|
multiple=True,
|
||||||
|
custom_value=True,
|
||||||
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_ATTRIBUTE): AttributeSelector(
|
||||||
|
AttributeSelectorConfig(entity_id=entity_id)
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_UPPER_LIMIT, default=False): BooleanSelector(),
|
||||||
|
vol.Optional(CONF_LOWER_LIMIT, default=False): BooleanSelector(),
|
||||||
|
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): NumberSelector(
|
||||||
|
NumberSelectorConfig(min=0, step=1, mode=NumberSelectorMode.BOX)
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_DEGREE, default=DEFAULT_DEGREE): NumberSelector(
|
||||||
|
NumberSelectorConfig(min=0, max=7, step=1, mode=NumberSelectorMode.BOX)
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): TextSelector(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_options(
|
||||||
|
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Validate options selected."""
|
||||||
|
|
||||||
|
user_input[CONF_PRECISION] = int(user_input[CONF_PRECISION])
|
||||||
|
user_input[CONF_DEGREE] = int(user_input[CONF_DEGREE])
|
||||||
|
|
||||||
|
for datapoint in user_input[CONF_DATAPOINTS]:
|
||||||
|
if not isinstance(datapoint, list):
|
||||||
|
raise SchemaFlowError("incorrect_datapoints")
|
||||||
|
|
||||||
|
if len(user_input[CONF_DATAPOINTS]) <= user_input[CONF_DEGREE]:
|
||||||
|
raise SchemaFlowError("not_enough_datapoints")
|
||||||
|
|
||||||
|
handler.parent_handler._async_abort_entries_match({**handler.options, **user_input}) # noqa: SLF001
|
||||||
|
|
||||||
|
return user_input
|
||||||
|
|
||||||
|
|
||||||
|
DATA_SCHEMA_SETUP = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_NAME, default=DEFAULT_NAME): TextSelector(),
|
||||||
|
vol.Required(CONF_ENTITY_ID): EntitySelector(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_FLOW = {
|
||||||
|
"user": SchemaFlowFormStep(
|
||||||
|
schema=DATA_SCHEMA_SETUP,
|
||||||
|
next_step="options",
|
||||||
|
),
|
||||||
|
"options": SchemaFlowFormStep(
|
||||||
|
schema=get_options_schema,
|
||||||
|
validate_user_input=validate_options,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
OPTIONS_FLOW = {
|
||||||
|
"init": SchemaFlowFormStep(
|
||||||
|
get_options_schema,
|
||||||
|
validate_user_input=validate_options,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Compensation."""
|
||||||
|
|
||||||
|
config_flow = CONFIG_FLOW
|
||||||
|
options_flow = OPTIONS_FLOW
|
||||||
|
|
||||||
|
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
|
||||||
|
"""Return config entry title."""
|
||||||
|
return cast(str, options[CONF_NAME])
|
@ -1,6 +1,9 @@
|
|||||||
"""Compensation constants."""
|
"""Compensation constants."""
|
||||||
|
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
DOMAIN = "compensation"
|
DOMAIN = "compensation"
|
||||||
|
PLATFORMS = [Platform.SENSOR]
|
||||||
|
|
||||||
SENSOR = "compensation"
|
SENSOR = "compensation"
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
"domain": "compensation",
|
"domain": "compensation",
|
||||||
"name": "Compensation",
|
"name": "Compensation",
|
||||||
"codeowners": ["@Petro31"],
|
"codeowners": ["@Petro31"],
|
||||||
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/compensation",
|
"documentation": "https://www.home-assistant.io/integrations/compensation",
|
||||||
|
"integration_type": "helper",
|
||||||
"iot_class": "calculated",
|
"iot_class": "calculated",
|
||||||
"requirements": ["numpy==1.26.0"]
|
"requirements": ["numpy==1.26.0"]
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ from typing import Any
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONF_ATTRIBUTE,
|
CONF_ATTRIBUTE,
|
||||||
@ -80,6 +81,36 @@ async def async_setup_platform(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Compensation sensor entry."""
|
||||||
|
compensation = entry.entry_id
|
||||||
|
conf: dict[str, Any] = hass.data[DATA_COMPENSATION][compensation]
|
||||||
|
|
||||||
|
source: str = conf[CONF_SOURCE]
|
||||||
|
attribute: str | None = conf.get(CONF_ATTRIBUTE)
|
||||||
|
name = entry.title
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
CompensationSensor(
|
||||||
|
conf.get(CONF_UNIQUE_ID),
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
attribute,
|
||||||
|
conf[CONF_PRECISION],
|
||||||
|
conf[CONF_POLYNOMIAL],
|
||||||
|
conf.get(CONF_UNIT_OF_MEASUREMENT),
|
||||||
|
conf[CONF_MINIMUM],
|
||||||
|
conf[CONF_MAXIMUM],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CompensationSensor(SensorEntity):
|
class CompensationSensor(SensorEntity):
|
||||||
"""Representation of a Compensation sensor."""
|
"""Representation of a Compensation sensor."""
|
||||||
|
|
||||||
|
77
homeassistant/components/compensation/strings.json
Normal file
77
homeassistant/components/compensation/strings.json
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"incorrect_datapoints": "Datapoints needs to be provided in list-format, ex. '[1.0, 0.0]'.",
|
||||||
|
"not_enough_datapoints": "The number of datapoints needs to be less or equal to configured degree."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"description": "Add a compensation sensor",
|
||||||
|
"data": {
|
||||||
|
"name": "[%key:common::config_flow::data::name%]",
|
||||||
|
"entity_id": "Entity"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"name": "Name for the created entity.",
|
||||||
|
"entity_id": "Entity to use as source."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"description": "Read the documention for further details on how to configure the statistics sensor using these options.",
|
||||||
|
"data": {
|
||||||
|
"data_points": "Data points",
|
||||||
|
"attribute": "Attribute",
|
||||||
|
"upper_limit": "Upper limit",
|
||||||
|
"lower_limit": "Lower limit",
|
||||||
|
"precision": "Precision",
|
||||||
|
"degree": "Degree",
|
||||||
|
"unit_of_measurement": "Unit of measurement"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"data_points": "The collection of data point conversions with the format '[uncompensated_value, compensated_value]'",
|
||||||
|
"attribute": "Attribute from the source to monitor/compensate.",
|
||||||
|
"upper_limit": "Enables an upper limit for the sensor. The upper limit is defined by the data collections (data_points) greatest uncompensated value.",
|
||||||
|
"lower_limit": "Enables a lower limit for the sensor. The lower limit is defined by the data collections (data_points) lowest uncompensated value.",
|
||||||
|
"precision": "Defines the precision of the calculated values, through the argument of round().",
|
||||||
|
"degree": "The degree of a polynomial.",
|
||||||
|
"unit_of_measurement": "Defines the units of measurement of the sensor, if any."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"incorrect_datapoints": "[%key:component::compensation::config::error::incorrect_datapoints%]",
|
||||||
|
"not_enough_datapoints": "[%key:component::compensation::config::error::not_enough_datapoints%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"description": "[%key:component::compensation::config::step::options::description%]",
|
||||||
|
"data": {
|
||||||
|
"data_points": "[%key:component::compensation::config::step::options::data::data_points%]",
|
||||||
|
"attribute": "[%key:component::compensation::config::step::options::data::attribute%]",
|
||||||
|
"upper_limit": "[%key:component::compensation::config::step::options::data::upper_limit%]",
|
||||||
|
"lower_limit": "[%key:component::compensation::config::step::options::data::lower_limit%]",
|
||||||
|
"precision": "[%key:component::compensation::config::step::options::data::precision%]",
|
||||||
|
"degree": "[%key:component::compensation::config::step::options::data::degree%]",
|
||||||
|
"unit_of_measurement": "[%key:component::compensation::config::step::options::data::unit_of_measurement%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"data_points": "[%key:component::compensation::config::step::options::data_description::data_points%]",
|
||||||
|
"attribute": "[%key:component::compensation::config::step::options::data_description::attribute%]",
|
||||||
|
"upper_limit": "[%key:component::compensation::config::step::options::data_description::upper_limit%]",
|
||||||
|
"lower_limit": "[%key:component::compensation::config::step::options::data_description::lower_limit%]",
|
||||||
|
"precision": "[%key:component::compensation::config::step::options::data_description::precision%]",
|
||||||
|
"degree": "[%key:component::compensation::config::step::options::data_description::degree%]",
|
||||||
|
"unit_of_measurement": "[%key:component::compensation::config::step::options::data_description::unit_of_measurement%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ To update, run python3 -m script.hassfest
|
|||||||
|
|
||||||
FLOWS = {
|
FLOWS = {
|
||||||
"helper": [
|
"helper": [
|
||||||
|
"compensation",
|
||||||
"derivative",
|
"derivative",
|
||||||
"generic_hygrostat",
|
"generic_hygrostat",
|
||||||
"generic_thermostat",
|
"generic_thermostat",
|
||||||
|
@ -1018,12 +1018,6 @@
|
|||||||
"config_flow": false,
|
"config_flow": false,
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
},
|
},
|
||||||
"compensation": {
|
|
||||||
"name": "Compensation",
|
|
||||||
"integration_type": "hub",
|
|
||||||
"config_flow": false,
|
|
||||||
"iot_class": "calculated"
|
|
||||||
},
|
|
||||||
"concord232": {
|
"concord232": {
|
||||||
"name": "Concord232",
|
"name": "Concord232",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
@ -7162,6 +7156,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
|
"compensation": {
|
||||||
|
"name": "Compensation",
|
||||||
|
"integration_type": "helper",
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "calculated"
|
||||||
|
},
|
||||||
"counter": {
|
"counter": {
|
||||||
"integration_type": "helper",
|
"integration_type": "helper",
|
||||||
"config_flow": false
|
"config_flow": false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user