Compare commits

..

7 Commits

Author SHA1 Message Date
Jean-Marc Collin
7e63c9aa79 Heater can restart after HVAC_MODE_OFF 2023-01-30 07:09:43 +01:00
Jean-Marc Collin
100cfaeac9 Add binary sensors for window sensors 2023-01-30 06:49:39 +01:00
Jean-Marc Collin
77631e94d9 FIX feature selection 2023-01-29 23:21:51 +01:00
Jean-Marc Collin
49c85eeb2b Try to fix const import 2023-01-29 22:01:05 +01:00
Jean-Marc Collin
bb2c1d328b FIX selectors 2023-01-29 21:39:08 +01:00
Jean-Marc Collin
b607fb1075 2.0.0-beta1
Issue #30 - security mode
Issue #26 - upodate entity on config change
Issue #21 - check box to select feature
Issue #16 - Suggest/select entity in a list on the configuration screens
Issue #5  - Thermostat over climate type
2023-01-29 19:53:49 +01:00
Jean-Marc Collin
4786949aeb Add availability to choose entity in Config Flow 2023-01-25 23:08:20 +01:00
15 changed files with 1317 additions and 502 deletions

6
.bashrc Normal file
View File

@@ -0,0 +1,6 @@
echo "Sourcing .bashrc"
alias ll='ls -l'
export HA='/home/vscode/core'
cd $HA
source venv/bin/activate

View File

@@ -7,6 +7,9 @@ logger:
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/) # If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
debugpy: debugpy:
start: true
wait: false
port: 5678
input_number: input_number:
fake_temperature_sensor1: fake_temperature_sensor1:
@@ -15,25 +18,28 @@ input_number:
max: 35 max: 35
step: .1 step: .1
icon: mdi:thermometer icon: mdi:thermometer
unit_of_measurement: °C
fake_external_temperature_sensor1: fake_external_temperature_sensor1:
name: Ext Temperature name: Ext Temperature
min: -10 min: -10
max: 35 max: 35
step: .1 step: .1
icon: mdi:home-thermometer icon: mdi:home-thermometer
unit_of_measurement: °C
fake_current_power: fake_current_power:
name: Current power name: Current power
min: 0 min: 0
max: 1000 max: 1000
step: 10 step: 10
icon: mdi:flash icon: mdi:flash
unit_of_measurement: kW
fake_current_power_max: fake_current_power_max:
name: Current power max threshold name: Current power max threshold
min: 0 min: 0
max: 1000 max: 1000
step: 10 step: 10
icon: mdi:flash icon: mdi:flash
unit_of_measurement: kW
input_boolean: input_boolean:
# input_boolean to simulate the windows entity. Only for development environment. # input_boolean to simulate the windows entity. Only for development environment.
@@ -58,3 +64,57 @@ input_boolean:
fake_presence_sensor1: fake_presence_sensor1:
name: Presence Sensor 1 name: Presence Sensor 1
icon: mdi:home icon: mdi:home
climate:
- platform: generic_thermostat
name: Underlying thermostat1
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat2
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat3
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat4
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat5
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat6
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat7
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat8
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat9
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
recorder:
include:
domains:
- input_boolean
- input_number
- switch
- climate
- sensor
template:
- binary_sensor:
- name: maison_occupee
unique_id: maison_occupee
state: "{{is_state('person.jmc', 'home') }}"
device_class: occupancy

View File

@@ -1,18 +1,25 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details. // See https://aka.ms/vscode-remote/devcontainer.json for format details.
// "image": "ghcr.io/ludeeus/devcontainer/integration:latest",
{ {
"image": "ghcr.io/ludeeus/devcontainer/integration:latest", "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.10",
"name": "Versatile Thermostat integration", "name": "Versatile Thermostat integration",
"context": "..", "context": "..",
"appPort": [ "appPort": [
"9123:8123" "9123:8123"
], ],
"postCreateCommand": "container install", // "postCreateCommand": "container install",
"postCreateCommand": "./container install",
"extensions": [ "extensions": [
"ms-python.python", "ms-python.python",
"github.vscode-pull-request-github", "github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters", "ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance" "ms-python.vscode-pylance"
], ],
"mounts": [
"source=/Users/jmcollin/SugarSync/Projets/home-assistant/core,target=/home/vscode/core,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=/home/vscode/core/config/configuration.yaml,type=bind,consistency=cached",
"source=${localWorkspaceFolder}/custom_components,target=/home/vscode/core/config/custom_components,type=bind,consistency=cached"
],
"settings": { "settings": {
"files.eol": "\n", "files.eol": "\n",
"editor.tabSize": 4, "editor.tabSize": 4,
@@ -25,7 +32,7 @@
"terminal.integrated.defaultProfile.linux": "Bash Profile", "terminal.integrated.defaultProfile.linux": "Bash Profile",
// "terminal.integrated.shell.linux": "/bin/bash", // "terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3", "python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false, "python.analysis.autoSearchPaths": true,
"python.linting.pylintEnabled": true, "python.linting.pylintEnabled": true,
"python.linting.enabled": true, "python.linting.enabled": true,
"python.formatting.provider": "black", "python.formatting.provider": "black",

8
.vscode/launch.json vendored
View File

@@ -11,9 +11,13 @@
"host": "localhost", "host": "localhost",
"justMyCode": false, "justMyCode": false,
"pathMappings": [ "pathMappings": [
// {
// "localRoot": "${workspaceFolder}",
// "remoteRoot": "."
//},
{ {
"localRoot": "${workspaceFolder}", "localRoot": "${workspaceFolder}/../core",
"remoteRoot": "." "remoteRoot": "/home/vscode/core"
} }
] ]
}, },

View File

@@ -4,5 +4,9 @@
"python.pythonPath": "/usr/local/bin/python", "python.pythonPath": "/usr/local/bin/python",
"files.associations": { "files.associations": {
"*.yaml": "home-assistant" "*.yaml": "home-assistant"
} },
"python.analysis.extraPaths": [
"/home/vscode/core",
"/workspaces/versatile_thermostat"
]
} }

26
.vscode/tasks.json vendored
View File

@@ -4,25 +4,43 @@
{ {
"label": "Run Home Assistant on port 9123", "label": "Run Home Assistant on port 9123",
"type": "shell", "type": "shell",
"command": "container start", "command": "./container start",
"problemMatcher": []
},
{
"label": "Restart Home Assistant on port 9123",
"type": "shell",
"command": "./container restart",
"problemMatcher": []
},
{
"label": "Home Assistant translations update",
"type": "shell",
"command": "./container translations",
"problemMatcher": []
},
{
"label": "Home Assistant hassfest",
"type": "shell",
"command": "./container hassfest",
"problemMatcher": [] "problemMatcher": []
}, },
{ {
"label": "Run Home Assistant configuration against /config", "label": "Run Home Assistant configuration against /config",
"type": "shell", "type": "shell",
"command": "container check-config", "command": "./container check-config",
"problemMatcher": [] "problemMatcher": []
}, },
{ {
"label": "Upgrade Home Assistant to latest dev", "label": "Upgrade Home Assistant to latest dev",
"type": "shell", "type": "shell",
"command": "container install", "command": "./container install",
"problemMatcher": [] "problemMatcher": []
}, },
{ {
"label": "Install a specific version of Home Assistant", "label": "Install a specific version of Home Assistant",
"type": "shell", "type": "shell",
"command": "container set-version", "command": "./container set-version",
"problemMatcher": [] "problemMatcher": []
} }
] ]

37
container Executable file
View File

@@ -0,0 +1,37 @@
#!/bin/bash
# set -x
. .bashrc
cd $HA
echo "arguments are: "$*
# Post installation of container
command=$1
if [ "$command" == "install" ]; then
echo "Running container post installation"
script/setup
fi
if [ "$command" == "start" ]; then
echo "Running container start"
hass -c ./config --debug
fi
if [ "$command" == "translations" ]; then
echo "Running container start"
python3 -m script.translations develop
fi
if [ "$command" == "hassfest" ]; then
echo "Running container start"
python3 -m script.hassfest
fi
if [ "$command" == "restart" ]; then
echo "Killing existing container"
pkill hass
echo "Killing existing container"
hass -c ./config
fi

View File

@@ -0,0 +1 @@
/home/vscode/core/homeassistant

View File

@@ -29,20 +29,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# hass.data.setdefault(DOMAIN, {}) # hass.data.setdefault(DOMAIN, {})
# TODO 1. Create API instance
api: VersatileThermostatAPI = hass.data.get(DOMAIN) api: VersatileThermostatAPI = hass.data.get(DOMAIN)
if api is None: if api is None:
api = VersatileThermostatAPI(hass) api = VersatileThermostatAPI(hass)
# TODO 2. Validate the API connection (and authentication)
# TODO 3. Store an API object for your platforms to access
api.add_entry(entry) api.add_entry(entry)
entry.async_on_unload(entry.add_update_listener(update_listener))
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
api: VersatileThermostatAPI = hass.data.get(DOMAIN) api: VersatileThermostatAPI = hass.data.get(DOMAIN)
@@ -89,3 +93,21 @@ class VersatileThermostatAPI(Dict):
def hass(self): def hass(self):
"""Get the HomeAssistant object""" """Get the HomeAssistant object"""
return self._hass return self._hass
# Example migration function
async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
if config_entry.version == 1:
new = {**config_entry.data}
# TODO: modify Config Entry data
config_entry.version = 2
hass.config_entries.async_update_entry(config_entry, data=new)
_LOGGER.info("Migration to version %s successful", config_entry.version)
return True

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,46 @@
"""Config flow for Versatile Thermostat integration.""" """Config flow for Versatile Thermostat integration."""
from __future__ import annotations from __future__ import annotations
from typing import Any
import logging import logging
import copy import copy
from collections.abc import Mapping from collections.abc import Mapping
import voluptuous as vol import voluptuous as vol
from typing import Any from homeassistant.exceptions import HomeAssistantError
from homeassistant.const import TEMPERATURE, UnitOfPower
from homeassistant.util.unit_system import TEMPERATURE_UNITS
from homeassistant.core import callback from homeassistant.core import callback, async_get_hass
from homeassistant.config_entries import ( from homeassistant.config_entries import (
ConfigEntry, ConfigEntry,
ConfigFlow as HAConfigFlow, ConfigFlow as HAConfigFlow,
OptionsFlow, OptionsFlow,
) )
# import homeassistant.helpers.entity_registry as entity_registry
from homeassistant.data_entry_flow import FlowHandler from homeassistant.data_entry_flow import FlowHandler
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_registry import (
RegistryEntry,
async_get,
)
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.input_boolean import (
DOMAIN as INPUT_BOOLEAN_DOMAIN,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.input_number import (
DOMAIN as INPUT_NUMBER_DOMAIN,
)
from homeassistant.components.person import DOMAIN as PERSON_DOMAIN
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from .const import ( from .const import (
DOMAIN, DOMAIN,
@@ -51,12 +71,17 @@ from .const import (
CONF_MINIMAL_ACTIVATION_DELAY, CONF_MINIMAL_ACTIVATION_DELAY,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_TEMP_MIN, CONF_TEMP_MIN,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_CLIMATE,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_THERMOSTAT_TYPES,
UnknownEntity,
) )
from .climate import VersatileThermostat
# from .climate import VersatileThermostat
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -101,6 +126,37 @@ def add_suggested_values_to_schema(
return vol.Schema(schema) return vol.Schema(schema)
def is_temperature_sensor(sensor: RegistryEntry):
"""Check if a registryEntry is a temperature sensor or assimilable to a temperature sensor"""
if not sensor.entity_id.startswith(
INPUT_NUMBER_DOMAIN
) and not sensor.entity_id.startswith(SENSOR_DOMAIN):
return False
return (
sensor.device_class == TEMPERATURE
or sensor.original_device_class == TEMPERATURE
or sensor.unit_of_measurement in TEMPERATURE_UNITS
)
def is_power_sensor(sensor: RegistryEntry):
"""Check if a registryEntry is a power sensor or assimilable to a temperature sensor"""
if not sensor.entity_id.startswith(
INPUT_NUMBER_DOMAIN
) and not sensor.entity_id.startswith(SENSOR_DOMAIN):
return False
return (
# sensor.device_class == TEMPERATURE
# or sensor.original_device_class == TEMPERATURE
sensor.unit_of_measurement
in [
UnitOfPower.KILO_WATT,
UnitOfPower.WATT,
UnitOfPower.BTU_PER_HOUR,
]
)
class VersatileThermostatBaseConfigFlow(FlowHandler): class VersatileThermostatBaseConfigFlow(FlowHandler):
"""The base Config flow class. Used to put some code in commons.""" """The base Config flow class. Used to put some code in commons."""
@@ -111,13 +167,84 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
super().__init__() super().__init__()
_LOGGER.debug("CTOR BaseConfigFlow infos: %s", infos) _LOGGER.debug("CTOR BaseConfigFlow infos: %s", infos)
self._infos = infos self._infos = infos
is_empty: bool = not bool(infos)
# Fix features selection depending to infos
self._infos[CONF_USE_WINDOW_FEATURE] = (
is_empty or self._infos.get(CONF_WINDOW_SENSOR) is not None
)
self._infos[CONF_USE_MOTION_FEATURE] = (
is_empty or self._infos.get(CONF_MOTION_SENSOR) is not None
)
self._infos[CONF_USE_POWER_FEATURE] = is_empty or (
self._infos.get(CONF_POWER_SENSOR) is not None
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
)
self._infos[CONF_USE_PRESENCE_FEATURE] = (
is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None
)
self.hass = async_get_hass()
ent_reg = async_get(hass=self.hass)
climates = []
switches = []
temp_sensors = []
power_sensors = []
window_sensors = []
presence_sensors = []
k: str
for k in ent_reg.entities:
v: RegistryEntry = ent_reg.entities[k]
_LOGGER.debug("Looking entity: %s", k)
# if k.startswith(CLIMATE_DOMAIN) and (
# infos is None or k != infos.get("entity_id")
# ):
# _LOGGER.debug("Climate !")
# climates.append(k)
if k.startswith(SWITCH_DOMAIN) or k.startswith(INPUT_BOOLEAN_DOMAIN):
_LOGGER.debug("Switch !")
switches.append(k)
elif is_temperature_sensor(v):
_LOGGER.debug("Temperature sensor !")
temp_sensors.append(k)
elif is_power_sensor(v):
_LOGGER.debug("Power sensor !")
power_sensors.append(k)
elif k.startswith(PERSON_DOMAIN):
_LOGGER.debug("Presence sensor !")
presence_sensors.append(k)
# window sensor and presence
if k.startswith(INPUT_BOOLEAN_DOMAIN) or k.startswith(BINARY_SENSOR_DOMAIN):
_LOGGER.debug("Window or presence sensor !")
window_sensors.append(k)
presence_sensors.append(k)
# Special case for climates which are not in EntityRegistry
climates = self.find_all_climates()
self.STEP_USER_DATA_SCHEMA = vol.Schema( self.STEP_USER_DATA_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HEATER): cv.string, vol.Required(
vol.Required(CONF_TEMP_SENSOR): cv.string, CONF_THERMOSTAT_TYPE, default=CONF_THERMOSTAT_SWITCH
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): cv.string, ): vol.In(CONF_THERMOSTAT_TYPES),
vol.Required(CONF_TEMP_SENSOR): vol.In(temp_sensors),
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): vol.In(temp_sensors),
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int, vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float),
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float),
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_MOTION_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_POWER_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_PRESENCE_FEATURE, default=False): cv.boolean,
}
)
self.STEP_THERMOSTAT_SWITCH = vol.Schema(
{
vol.Required(CONF_HEATER): vol.In(switches),
vol.Required( vol.Required(
CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI
): vol.In( ): vol.In(
@@ -125,8 +252,12 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
] ]
), ),
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float), }
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float), )
self.STEP_THERMOSTAT_CLIMATE = vol.Schema(
{
vol.Required(CONF_CLIMATE): vol.In(climates),
} }
) )
@@ -146,14 +277,14 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
self.STEP_WINDOW_DATA_SCHEMA = vol.Schema( self.STEP_WINDOW_DATA_SCHEMA = vol.Schema(
{ {
vol.Optional(CONF_WINDOW_SENSOR): cv.string, vol.Optional(CONF_WINDOW_SENSOR): vol.In(window_sensors),
vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int, vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int,
} }
) )
self.STEP_MOTION_DATA_SCHEMA = vol.Schema( self.STEP_MOTION_DATA_SCHEMA = vol.Schema(
{ {
vol.Optional(CONF_MOTION_SENSOR): cv.string, vol.Optional(CONF_MOTION_SENSOR): vol.In(window_sensors),
vol.Optional(CONF_MOTION_DELAY, default=30): cv.positive_int, vol.Optional(CONF_MOTION_DELAY, default=30): cv.positive_int,
vol.Optional(CONF_MOTION_PRESET, default="comfort"): vol.In( vol.Optional(CONF_MOTION_PRESET, default="comfort"): vol.In(
CONF_PRESETS_SELECTIONABLE CONF_PRESETS_SELECTIONABLE
@@ -166,16 +297,16 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
self.STEP_POWER_DATA_SCHEMA = vol.Schema( self.STEP_POWER_DATA_SCHEMA = vol.Schema(
{ {
vol.Optional(CONF_POWER_SENSOR): cv.string, vol.Optional(CONF_POWER_SENSOR): vol.In(power_sensors),
vol.Optional(CONF_MAX_POWER_SENSOR): cv.string, vol.Optional(CONF_MAX_POWER_SENSOR): vol.In(power_sensors),
vol.Optional(CONF_DEVICE_POWER): vol.Coerce(float), vol.Optional(CONF_DEVICE_POWER, default="1"): vol.Coerce(float),
vol.Optional(CONF_PRESET_POWER): vol.Coerce(float), vol.Optional(CONF_PRESET_POWER, default="13"): vol.Coerce(float),
} }
) )
self.STEP_PRESENCE_DATA_SCHEMA = vol.Schema( self.STEP_PRESENCE_DATA_SCHEMA = vol.Schema(
{ {
vol.Optional(CONF_PRESENCE_SENSOR): cv.string, vol.Optional(CONF_PRESENCE_SENSOR): vol.In(presence_sensors),
} }
).extend( ).extend(
{ {
@@ -209,6 +340,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_POWER_SENSOR, CONF_POWER_SENSOR,
CONF_MAX_POWER_SENSOR, CONF_MAX_POWER_SENSOR,
CONF_PRESENCE_SENSOR, CONF_PRESENCE_SENSOR,
CONF_CLIMATE,
]: ]:
d = data.get(conf, None) # pylint: disable=invalid-name d = data.get(conf, None) # pylint: disable=invalid-name
if d is not None and self.hass.states.get(d) is None: if d is not None and self.hass.states.get(d) is None:
@@ -268,9 +400,25 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.debug("Into ConfigFlow.async_step_user user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_user user_input=%s", user_input)
return await self.generic_step( return await self.generic_step(
"user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi "user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_type
) )
async def async_step_type(self, user_input: dict | None = None) -> FlowResult:
"""Handle the flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input)
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
return await self.generic_step(
"type", self.STEP_THERMOSTAT_SWITCH, user_input, self.async_step_tpi
)
else:
return await self.generic_step(
"type",
self.STEP_THERMOSTAT_CLIMATE,
user_input,
self.async_step_presets,
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult: async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
"""Handle the flow steps""" """Handle the flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)
@@ -283,35 +431,63 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the presets flow steps""" """Handle the presets flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_presets user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_presets user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_WINDOW_FEATURE]:
next_step = self.async_step_window
elif self._infos[CONF_USE_MOTION_FEATURE]:
next_step = self.async_step_motion
elif self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, self.async_step_window "presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, next_step
) )
async def async_step_window(self, user_input: dict | None = None) -> FlowResult: async def async_step_window(self, user_input: dict | None = None) -> FlowResult:
"""Handle the window sensor flow steps""" """Handle the window sensor flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_window user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_window user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_MOTION_FEATURE]:
next_step = self.async_step_motion
elif self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"window", self.STEP_WINDOW_DATA_SCHEMA, user_input, self.async_step_motion "window", self.STEP_WINDOW_DATA_SCHEMA, user_input, next_step
) )
async def async_step_motion(self, user_input: dict | None = None) -> FlowResult: async def async_step_motion(self, user_input: dict | None = None) -> FlowResult:
"""Handle the window and motion sensor flow steps""" """Handle the window and motion sensor flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_motion user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_motion user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"motion", self.STEP_MOTION_DATA_SCHEMA, user_input, self.async_step_power "motion", self.STEP_MOTION_DATA_SCHEMA, user_input, next_step
) )
async def async_step_power(self, user_input: dict | None = None) -> FlowResult: async def async_step_power(self, user_input: dict | None = None) -> FlowResult:
"""Handle the power management flow steps""" """Handle the power management flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_power user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_power user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"power", "power",
self.STEP_POWER_DATA_SCHEMA, self.STEP_POWER_DATA_SCHEMA,
user_input, user_input,
self.async_step_presence, next_step,
) )
async def async_step_presence(self, user_input: dict | None = None) -> FlowResult: async def async_step_presence(self, user_input: dict | None = None) -> FlowResult:
@@ -336,6 +512,21 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
self.async_finalize, # pylint: disable=no-member self.async_finalize, # pylint: disable=no-member
) )
async def async_finalize(self):
"""Should be implemented by Leaf classes"""
raise HomeAssistantError(
"async_finalize not implemented on VersatileThermostat sub-class"
)
def find_all_climates(self) -> list(str):
"""Find all climate known by HA"""
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
ret: list(str) = list()
for entity in component.entities:
ret.append(entity.entity_id)
_LOGGER.debug("Found all climate entities: %s", ret)
return ret
class VersatileThermostatConfigFlow( class VersatileThermostatConfigFlow(
VersatileThermostatBaseConfigFlow, HAConfigFlow, domain=DOMAIN VersatileThermostatBaseConfigFlow, HAConfigFlow, domain=DOMAIN
@@ -359,16 +550,12 @@ class VersatileThermostatConfigFlow(
return self.async_create_entry(title=self._infos[CONF_NAME], data=self._infos) return self.async_create_entry(title=self._infos[CONF_NAME], data=self._infos)
class UnknownEntity(HomeAssistantError):
"""Error to indicate there is an unknown entity_id given."""
class VersatileThermostatOptionsFlowHandler( class VersatileThermostatOptionsFlowHandler(
VersatileThermostatBaseConfigFlow, OptionsFlow VersatileThermostatBaseConfigFlow, OptionsFlow
): ):
"""Handle options flow for Versatile Thermostat integration.""" """Handle options flow for Versatile Thermostat integration."""
def __init__(self, config_entry: ConfigEntry): def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow.""" """Initialize options flow."""
super().__init__(config_entry.data.copy()) super().__init__(config_entry.data.copy())
self.config_entry = config_entry self.config_entry = config_entry
@@ -394,9 +581,27 @@ class VersatileThermostatOptionsFlowHandler(
) )
return await self.generic_step( return await self.generic_step(
"user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi "user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_type
) )
async def async_step_type(self, user_input: dict | None = None) -> FlowResult:
"""Handle the flow steps"""
_LOGGER.debug(
"Into OptionsFlowHandler.async_step_user user_input=%s", user_input
)
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
return await self.generic_step(
"type", self.STEP_THERMOSTAT_SWITCH, user_input, self.async_step_tpi
)
else:
return await self.generic_step(
"type",
self.STEP_THERMOSTAT_CLIMATE,
user_input,
self.async_step_presets,
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult: async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
"""Handle the tpi flow steps""" """Handle the tpi flow steps"""
_LOGGER.debug( _LOGGER.debug(
@@ -413,8 +618,18 @@ class VersatileThermostatOptionsFlowHandler(
"Into OptionsFlowHandler.async_step_presets user_input=%s", user_input "Into OptionsFlowHandler.async_step_presets user_input=%s", user_input
) )
next_step = self.async_step_advanced
if self._infos[CONF_USE_WINDOW_FEATURE]:
next_step = self.async_step_window
elif self._infos[CONF_USE_MOTION_FEATURE]:
next_step = self.async_step_motion
elif self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, self.async_step_window "presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, next_step
) )
async def async_step_window(self, user_input: dict | None = None) -> FlowResult: async def async_step_window(self, user_input: dict | None = None) -> FlowResult:
@@ -423,8 +638,15 @@ class VersatileThermostatOptionsFlowHandler(
"Into OptionsFlowHandler.async_step_window user_input=%s", user_input "Into OptionsFlowHandler.async_step_window user_input=%s", user_input
) )
next_step = self.async_step_advanced
if self._infos[CONF_USE_MOTION_FEATURE]:
next_step = self.async_step_motion
elif self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"window", self.STEP_WINDOW_DATA_SCHEMA, user_input, self.async_step_motion "window", self.STEP_WINDOW_DATA_SCHEMA, user_input, next_step
) )
async def async_step_motion(self, user_input: dict | None = None) -> FlowResult: async def async_step_motion(self, user_input: dict | None = None) -> FlowResult:
@@ -433,8 +655,14 @@ class VersatileThermostatOptionsFlowHandler(
"Into OptionsFlowHandler.async_step_motion user_input=%s", user_input "Into OptionsFlowHandler.async_step_motion user_input=%s", user_input
) )
next_step = self.async_step_advanced
if self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"motion", self.STEP_MOTION_DATA_SCHEMA, user_input, self.async_step_power "motion", self.STEP_MOTION_DATA_SCHEMA, user_input, next_step
) )
async def async_step_power(self, user_input: dict | None = None) -> FlowResult: async def async_step_power(self, user_input: dict | None = None) -> FlowResult:
@@ -443,11 +671,15 @@ class VersatileThermostatOptionsFlowHandler(
"Into OptionsFlowHandler.async_step_power user_input=%s", user_input "Into OptionsFlowHandler.async_step_power user_input=%s", user_input
) )
next_step = self.async_step_advanced
if self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"power", "power",
self.STEP_POWER_DATA_SCHEMA, self.STEP_POWER_DATA_SCHEMA,
user_input, user_input,
self.async_step_presence, next_step,
) )
async def async_step_presence(self, user_input: dict | None = None) -> FlowResult: async def async_step_presence(self, user_input: dict | None = None) -> FlowResult:
@@ -478,34 +710,20 @@ class VersatileThermostatOptionsFlowHandler(
async def async_end(self): async def async_end(self):
"""Finalization of the ConfigEntry creation""" """Finalization of the ConfigEntry creation"""
_LOGGER.debug( if not self._infos[CONF_USE_WINDOW_FEATURE]:
"CTOR ConfigFlow.async_finalize - updating entry with: %s", self._infos self._infos[CONF_WINDOW_SENSOR] = None
if not self._infos[CONF_USE_MOTION_FEATURE]:
self._infos[CONF_MOTION_SENSOR] = None
if not self._infos[CONF_USE_POWER_FEATURE]:
self._infos[CONF_POWER_SENSOR] = None
self._infos[CONF_MAX_POWER_SENSOR] = None
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
self._infos[CONF_PRESENCE_SENSOR] = None
_LOGGER.info(
"Recreating entry %s due to configuration change. New config is now: %s",
self.config_entry.entry_id,
self._infos,
) )
# Find eventual existing entity to update it
# removing entities from registry (they will be recreated)
# ent_reg = entity_registry.async_get(self.hass)
# reg_entities = {
# ent.unique_id: ent.entity_id
# for ent in entity_registry.async_entries_for_config_entry(
# ent_reg, self.config_entry.entry_id
# )
# }
#
# for entry in entity_registry.async_entries_for_config_entry(
# ent_reg, self.config_entry.entry_id
# ):
# entity: VersatileThermostat = ent_reg.async_get(entry.entity_id)
# entity.async_registry_entry_updated(self._infos)
_LOGGER.debug(
"We have found entities to update: %s", self.config_entry.entry_id
)
await VersatileThermostat.update_entity(self.config_entry.entry_id, self._infos)
# for entity_id in reg_entities.values():
# _LOGGER.info("Recreating entity %s due to configuration change", entity_id)
# ent_reg.async_remove(entity_id)
#
self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos) self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos)
return self.async_create_entry(title=None, data=None) return self.async_create_entry(title=None, data=None)

View File

@@ -9,6 +9,8 @@ from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
) )
from homeassistant.exceptions import HomeAssistantError
from .prop_algorithm import ( from .prop_algorithm import (
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
) )
@@ -42,6 +44,14 @@ CONF_MINIMAL_ACTIVATION_DELAY = "minimal_activation_delay"
CONF_TEMP_MIN = "temp_min" CONF_TEMP_MIN = "temp_min"
CONF_TEMP_MAX = "temp_max" CONF_TEMP_MAX = "temp_max"
CONF_SECURITY_DELAY_MIN = "security_delay_min" CONF_SECURITY_DELAY_MIN = "security_delay_min"
CONF_THERMOSTAT_TYPE = "thermostat_type"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
CONF_CLIMATE = "climate_entity_id"
CONF_USE_WINDOW_FEATURE = "use_window_feature"
CONF_USE_MOTION_FEATURE = "use_motion_feature"
CONF_USE_PRESENCE_FEATURE = "use_presence_feature"
CONF_USE_POWER_FEATURE = "use_power_feature"
CONF_PRESETS = { CONF_PRESETS = {
p: f"{p}_temp" p: f"{p}_temp"
@@ -92,6 +102,14 @@ ALL_CONF = (
CONF_TEMP_MIN, CONF_TEMP_MIN,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_SECURITY_DELAY_MIN, CONF_SECURITY_DELAY_MIN,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
] ]
+ CONF_PRESETS_VALUES + CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES, + CONF_PRESETS_AWAY_VALUES,
@@ -101,7 +119,13 @@ CONF_FUNCTIONS = [
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
] ]
CONF_THERMOSTAT_TYPES = [CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_CLIMATE]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence" SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature" SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
class UnknownEntity(HomeAssistantError):
"""Error to indicate there is an unknown entity_id given."""

View File

@@ -8,13 +8,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
}
},
"type": {
"title": "Linked entity",
"description": "Linked entity attributes",
"data": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying thermostat entity id"
} }
}, },
"tpi": { "tpi": {
@@ -97,13 +109,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
}
},
"type": {
"title": "Linked entity",
"description": "Linked entity attributes",
"data": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying thermostat entity id"
} }
}, },
"tpi": { "tpi": {
@@ -177,5 +201,13 @@
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
} }
},
"selector": {
"thermostat_type": {
"options": {
"thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over another thermostat"
}
}
} }
} }

View File

@@ -8,13 +8,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
}
},
"type": {
"title": "Linked entity",
"description": "Linked entity attributes",
"data": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying thermostat entity id"
} }
}, },
"tpi": { "tpi": {
@@ -97,13 +109,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
}
},
"type": {
"title": "Linked entity",
"description": "Linked entity attributes",
"data": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying thermostat entity id"
} }
}, },
"tpi": { "tpi": {
@@ -177,5 +201,13 @@
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
} }
},
"selector": {
"thermostat_type": {
"options": {
"thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over another thermostat"
}
}
} }
} }

View File

@@ -8,13 +8,24 @@
"description": "Principaux attributs obligatoires", "description": "Principaux attributs obligatoires",
"data": { "data": {
"name": "Nom", "name": "Nom",
"heater_entity_id": "Radiateur entity id",
"temperature_sensor_entity_id": "Température sensor entity id", "temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id", "external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise" "temp_max": "Température maximale permise",
"use_window_feature": "Avec détection des ouvertures",
"use_motion_feature": "Avec détection de mouvement",
"use_power_feature": "Avec gestion de la puissance",
"use_presence_feature": "Avec détection de présence"
}
},
"type": {
"title": "Entité liée",
"description": "Attributs de l'entité liée",
"data": {
"heater_entity_id": "Radiateur entity id",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Thermostat sous-jacent entity id"
} }
}, },
"tpi": { "tpi": {
@@ -97,13 +108,26 @@
"description": "Principaux attributs obligatoires", "description": "Principaux attributs obligatoires",
"data": { "data": {
"name": "Nom", "name": "Nom",
"heater_entity_id": "Radiateur entity id", "thermostat_over_switch": "Thermostat sur un switch",
"thermostat_over_climate": "Thermostat sur un autre thermostat",
"temperature_sensor_entity_id": "Température sensor entity id", "temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id", "external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise" "temp_max": "Température maximale permise",
"use_window_feature": "Avec détection des ouvertures",
"use_motion_feature": "Avec détection de mouvement",
"use_power_feature": "Avec gestion de la puissance",
"use_presence_feature": "Avec détection de présence"
}
},
"type": {
"title": "Entité liée",
"description": "Attributs de l'entité liée",
"data": {
"heater_entity_id": "Radiateur entity id",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Thermostat sous-jacent entity id"
} }
}, },
"tpi": { "tpi": {
@@ -177,5 +201,13 @@
"abort": { "abort": {
"already_configured": "Le device est déjà configuré" "already_configured": "Le device est déjà configuré"
} }
},
"selector": {
"thermostat_type": {
"options": {
"thermostat_over_switch": "Thermostat sur un switch",
"thermostat_over_climate": "Thermostat sur un autre thermostat"
}
}
} }
} }