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
This commit is contained in:
6
.bashrc
Normal file
6
.bashrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
echo "Sourcing .bashrc"
|
||||||
|
alias ll='ls -l'
|
||||||
|
export HA='/home/vscode/core'
|
||||||
|
cd $HA
|
||||||
|
source venv/bin/activate
|
||||||
@@ -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.
|
||||||
@@ -96,3 +102,12 @@ climate:
|
|||||||
name: Underlying thermostat9
|
name: Underlying thermostat9
|
||||||
heater: input_boolean.fake_heater_switch3
|
heater: input_boolean.fake_heater_switch3
|
||||||
target_sensor: input_number.fake_temperature_sensor1
|
target_sensor: input_number.fake_temperature_sensor1
|
||||||
|
|
||||||
|
recorder:
|
||||||
|
include:
|
||||||
|
domains:
|
||||||
|
- input_boolean
|
||||||
|
- input_number
|
||||||
|
- switch
|
||||||
|
- climate
|
||||||
|
- sensor
|
||||||
|
|||||||
@@ -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
8
.vscode/launch.json
vendored
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -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
26
.vscode/tasks.json
vendored
@@ -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
37
container
Executable 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
|
||||||
1
custom_components/homeassistant
Symbolic link
1
custom_components/homeassistant
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/home/vscode/core/homeassistant
|
||||||
@@ -93,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
|
||||||
|
|||||||
@@ -14,15 +14,18 @@ from homeassistant.core import (
|
|||||||
CoreState,
|
CoreState,
|
||||||
DOMAIN as HA_DOMAIN,
|
DOMAIN as HA_DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateEntity
|
from homeassistant.components.climate import ClimateEntity
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
|
from homeassistant.util.unit_system import UnitOfTemperature
|
||||||
|
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
async_call_later,
|
async_call_later,
|
||||||
|
async_track_time_interval,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.exceptions import ConditionError
|
from homeassistant.exceptions import ConditionError
|
||||||
@@ -32,6 +35,7 @@ from homeassistant.helpers import (
|
|||||||
) # , config_validation as cv
|
) # , config_validation as cv
|
||||||
|
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
|
DOMAIN as CLIMATE_DOMAIN,
|
||||||
ATTR_PRESET_MODE,
|
ATTR_PRESET_MODE,
|
||||||
# ATTR_FAN_MODE,
|
# ATTR_FAN_MODE,
|
||||||
CURRENT_HVAC_COOL,
|
CURRENT_HVAC_COOL,
|
||||||
@@ -51,12 +55,17 @@ from homeassistant.components.climate.const import (
|
|||||||
# PRESET_SLEEP,
|
# PRESET_SLEEP,
|
||||||
SUPPORT_PRESET_MODE,
|
SUPPORT_PRESET_MODE,
|
||||||
# SUPPORT_TARGET_TEMPERATURE,
|
# SUPPORT_TARGET_TEMPERATURE,
|
||||||
|
SERVICE_SET_FAN_MODE,
|
||||||
|
SERVICE_SET_HUMIDITY,
|
||||||
|
SERVICE_SET_HVAC_MODE,
|
||||||
|
# SERVICE_SET_PRESET_MODE,
|
||||||
|
SERVICE_SET_SWING_MODE,
|
||||||
|
SERVICE_SET_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
# UnitOfTemperature,
|
# UnitOfTemperature,
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
TEMP_CELSIUS,
|
|
||||||
# TEMP_FAHRENHEIT,
|
# TEMP_FAHRENHEIT,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
# CONF_UNIQUE_ID,
|
# CONF_UNIQUE_ID,
|
||||||
@@ -110,6 +119,7 @@ from .const import (
|
|||||||
# CONF_THERMOSTAT_SWITCH,
|
# CONF_THERMOSTAT_SWITCH,
|
||||||
CONF_THERMOSTAT_CLIMATE,
|
CONF_THERMOSTAT_CLIMATE,
|
||||||
CONF_CLIMATE,
|
CONF_CLIMATE,
|
||||||
|
UnknownEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .prop_algorithm import PropAlgorithm
|
from .prop_algorithm import PropAlgorithm
|
||||||
@@ -158,9 +168,6 @@ async def async_setup_entry(
|
|||||||
"service_set_preset_temperature",
|
"service_set_preset_temperature",
|
||||||
)
|
)
|
||||||
|
|
||||||
# A test to see if I'm able to get the entity
|
|
||||||
_LOGGER.error("Plaform entities are: %s", platform.entities)
|
|
||||||
|
|
||||||
|
|
||||||
class VersatileThermostat(ClimateEntity, RestoreEntity):
|
class VersatileThermostat(ClimateEntity, RestoreEntity):
|
||||||
"""Representation of a Versatile Thermostat device."""
|
"""Representation of a Versatile Thermostat device."""
|
||||||
@@ -210,6 +217,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._thermostat_type = None
|
self._thermostat_type = None
|
||||||
self._heater_entity_id = None
|
self._heater_entity_id = None
|
||||||
self._climate_entity_id = None
|
self._climate_entity_id = None
|
||||||
|
self._is_over_climate = False
|
||||||
|
self._underlying_climate = None
|
||||||
|
|
||||||
self.post_init(entry_infos)
|
self.post_init(entry_infos)
|
||||||
|
|
||||||
@@ -252,9 +261,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# Exploit usable attributs
|
# Exploit usable attributs
|
||||||
self._thermostat_type = entry_infos.get(CONF_THERMOSTAT_TYPE)
|
self._thermostat_type = entry_infos.get(CONF_THERMOSTAT_TYPE)
|
||||||
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
|
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
|
||||||
|
self._is_over_climate = True
|
||||||
self._climate_entity_id = entry_infos.get(CONF_CLIMATE)
|
self._climate_entity_id = entry_infos.get(CONF_CLIMATE)
|
||||||
else:
|
else:
|
||||||
self._heater_entity_id = entry_infos.get(CONF_HEATER)
|
self._heater_entity_id = entry_infos.get(CONF_HEATER)
|
||||||
|
self._is_over_climate = False
|
||||||
|
|
||||||
self._cycle_min = entry_infos.get(CONF_CYCLE_MIN)
|
self._cycle_min = entry_infos.get(CONF_CYCLE_MIN)
|
||||||
self._proportional_function = entry_infos.get(CONF_PROP_FUNCTION)
|
self._proportional_function = entry_infos.get(CONF_PROP_FUNCTION)
|
||||||
@@ -287,7 +298,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
||||||
# else:
|
# else:
|
||||||
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
self._unit = TEMP_CELSIUS
|
self._unit = UnitOfTemperature.CELSIUS
|
||||||
# Will be restored if possible
|
# Will be restored if possible
|
||||||
self._hvac_mode = None # HVAC_MODE_OFF
|
self._hvac_mode = None # HVAC_MODE_OFF
|
||||||
self._saved_hvac_mode = self._hvac_mode
|
self._saved_hvac_mode = self._hvac_mode
|
||||||
@@ -321,7 +332,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._current_power = 0
|
self._current_power = 0
|
||||||
self._current_power_max = 0
|
self._current_power_max = 0
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("%s - Power management is not fully configured.", self)
|
_LOGGER.info("%s - Power management is not fully configured", self)
|
||||||
|
|
||||||
# will be restored if possible
|
# will be restored if possible
|
||||||
self._target_temp = None
|
self._target_temp = None
|
||||||
@@ -353,6 +364,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# Initiate the ProportionalAlgorithm
|
# Initiate the ProportionalAlgorithm
|
||||||
if self._prop_algorithm is not None:
|
if self._prop_algorithm is not None:
|
||||||
del self._prop_algorithm
|
del self._prop_algorithm
|
||||||
|
if not self._is_over_climate:
|
||||||
self._prop_algorithm = PropAlgorithm(
|
self._prop_algorithm = PropAlgorithm(
|
||||||
self._proportional_function,
|
self._proportional_function,
|
||||||
self._tpi_coef_int,
|
self._tpi_coef_int,
|
||||||
@@ -481,15 +493,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
await self.async_startup()
|
await self.async_startup()
|
||||||
|
|
||||||
# starts the cycle
|
# starts a cycle if we are in over_climate type
|
||||||
# if self._cycle_min:
|
if self._is_over_climate:
|
||||||
# self.async_on_remove(
|
self.async_on_remove(
|
||||||
# async_track_time_interval(
|
async_track_time_interval(
|
||||||
# self.hass,
|
self.hass,
|
||||||
# self._async_control_heating,
|
self._async_control_heating,
|
||||||
# interval=timedelta(minutes=self._cycle_min),
|
interval=timedelta(minutes=self._cycle_min),
|
||||||
# )
|
)
|
||||||
# )
|
)
|
||||||
|
|
||||||
def async_remove_thermostat(self):
|
def async_remove_thermostat(self):
|
||||||
"""Called when the thermostat will be removed"""
|
"""Called when the thermostat will be removed"""
|
||||||
@@ -506,6 +518,32 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
async def _async_startup_internal(*_):
|
async def _async_startup_internal(*_):
|
||||||
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
||||||
need_write_state = False
|
need_write_state = False
|
||||||
|
|
||||||
|
# Get the underlying thermostat
|
||||||
|
if self._is_over_climate:
|
||||||
|
component: EntityComponent[ClimateEntity] = self.hass.data[
|
||||||
|
CLIMATE_DOMAIN
|
||||||
|
]
|
||||||
|
for entity in component.entities:
|
||||||
|
if self._climate_entity_id == entity.entity_id:
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - The underlying climate entity: %s have been succesfully found",
|
||||||
|
self,
|
||||||
|
entity,
|
||||||
|
)
|
||||||
|
self._underlying_climate = entity
|
||||||
|
break
|
||||||
|
if self._underlying_climate is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"%s - Cannot find the underlying climate entity: %s. Thermostat will not be operational",
|
||||||
|
self,
|
||||||
|
self._climate_entity_id,
|
||||||
|
)
|
||||||
|
self._is_over_climate = False
|
||||||
|
raise UnknownEntity(
|
||||||
|
f"Underlying thermostat {self._climate_entity_id} not found"
|
||||||
|
)
|
||||||
|
|
||||||
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
||||||
if temperature_state and temperature_state.state not in (
|
if temperature_state and temperature_state.state not in (
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
@@ -544,13 +582,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
|
if self._is_over_climate:
|
||||||
climate_state = self.hass.states.get(self._climate_entity_id)
|
climate_state = self.hass.states.get(self._climate_entity_id)
|
||||||
if climate_state and climate_state.state not in (
|
if climate_state and climate_state.state not in (
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
):
|
):
|
||||||
self._hvac_mode = climate_state
|
self._hvac_mode = climate_state.state
|
||||||
need_write_state = True
|
need_write_state = True
|
||||||
else:
|
else:
|
||||||
switch_state = self.hass.states.get(self._heater_entity_id)
|
switch_state = self.hass.states.get(self._heater_entity_id)
|
||||||
@@ -560,20 +598,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
):
|
):
|
||||||
self.hass.create_task(self._check_switch_initial_state())
|
self.hass.create_task(self._check_switch_initial_state())
|
||||||
|
|
||||||
platforms = entity_platform.async_get_platforms(
|
|
||||||
self._hass, "versatile_thermostat"
|
|
||||||
)
|
|
||||||
# A test to see if I'm able to get the entity
|
|
||||||
_LOGGER.error("Plaform entities are: %s", platforms[1].entities)
|
|
||||||
underclimate: VersatileThermostat = platforms[1].entities[
|
|
||||||
"climate.thermostat_2"
|
|
||||||
]
|
|
||||||
_LOGGER.error("plateform[1].entitie[thermostat_2 is: %s", underclimate)
|
|
||||||
_LOGGER.error("thermostat2.preset_modes is: %s", underclimate.preset_modes)
|
|
||||||
|
|
||||||
component: EntityComponent[ClimateEntity] = self._hass.data["climate"]
|
|
||||||
_LOGGER.error("component.entities is: %s", component.get_entity("climate.thermostat_2"))
|
|
||||||
|
|
||||||
if self._pmax_on:
|
if self._pmax_on:
|
||||||
# try to acquire current power and power max
|
# try to acquire current power and power max
|
||||||
current_power_state = self.hass.states.get(self._power_sensor_entity_id)
|
current_power_state = self.hass.states.get(self._power_sensor_entity_id)
|
||||||
@@ -634,7 +658,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._motion_state,
|
self._motion_state,
|
||||||
)
|
)
|
||||||
# recalculate the right target_temp in activity mode
|
# recalculate the right target_temp in activity mode
|
||||||
self._update_motion_temp()
|
await self._async_update_motion_temp()
|
||||||
need_write_state = True
|
need_write_state = True
|
||||||
|
|
||||||
if self._presence_on:
|
if self._presence_on:
|
||||||
@@ -644,7 +668,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
):
|
):
|
||||||
self._update_presence(presence_state.state)
|
await self._async_update_presence(presence_state.state)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - Presence have been retrieved: %s",
|
"%s - Presence have been retrieved: %s",
|
||||||
self,
|
self,
|
||||||
@@ -654,6 +678,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
if need_write_state:
|
if need_write_state:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
if self._prop_algorithm:
|
||||||
self._prop_algorithm.calculate(
|
self._prop_algorithm.calculate(
|
||||||
self._target_temp, self._cur_temp, self._cur_ext_temp
|
self._target_temp, self._cur_temp, self._cur_ext_temp
|
||||||
)
|
)
|
||||||
@@ -681,16 +706,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# If we have a previously saved temperature
|
# If we have a previously saved temperature
|
||||||
if old_state.attributes.get(ATTR_TEMPERATURE) is None:
|
if old_state.attributes.get(ATTR_TEMPERATURE) is None:
|
||||||
if self._ac_mode:
|
if self._ac_mode:
|
||||||
self._target_temp = self.max_temp
|
await self._async_internal_set_temperature(self.max_temp)
|
||||||
else:
|
else:
|
||||||
self._target_temp = self.min_temp
|
await self._async_internal_set_temperature(self.min_temp)
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - Undefined target temperature, falling back to %s",
|
"%s - Undefined target temperature, falling back to %s",
|
||||||
self,
|
self,
|
||||||
self._target_temp,
|
self._target_temp,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._target_temp = float(old_state.attributes[ATTR_TEMPERATURE])
|
await self._async_internal_set_temperature(
|
||||||
|
float(old_state.attributes[ATTR_TEMPERATURE])
|
||||||
|
)
|
||||||
|
|
||||||
if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes:
|
if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes:
|
||||||
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
|
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
|
||||||
@@ -699,18 +726,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if not self._hvac_mode and old_state.state:
|
if not self._hvac_mode and old_state.state:
|
||||||
self._hvac_mode = old_state.state
|
self._hvac_mode = old_state.state
|
||||||
|
|
||||||
# is done in startup above
|
|
||||||
# self._prop_algorithm.calculate(
|
|
||||||
# self._target_temp, self._cur_temp, self._cur_ext_temp
|
|
||||||
# )
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# No previous state, try and restore defaults
|
# No previous state, try and restore defaults
|
||||||
if self._target_temp is None:
|
if self._target_temp is None:
|
||||||
if self._ac_mode:
|
if self._ac_mode:
|
||||||
self._target_temp = self.max_temp
|
await self._async_internal_set_temperature(self.max_temp)
|
||||||
else:
|
else:
|
||||||
self._target_temp = self.min_temp
|
await self._async_internal_set_temperature(self.min_temp)
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No previously saved temperature, setting to %s", self._target_temp
|
"No previously saved temperature, setting to %s", self._target_temp
|
||||||
)
|
)
|
||||||
@@ -747,6 +769,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
@property
|
@property
|
||||||
def hvac_modes(self):
|
def hvac_modes(self):
|
||||||
"""List of available operation modes."""
|
"""List of available operation modes."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.hvac_modes
|
||||||
|
|
||||||
return self._hvac_list
|
return self._hvac_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -757,6 +782,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
@property
|
@property
|
||||||
def hvac_mode(self):
|
def hvac_mode(self):
|
||||||
"""Return current operation."""
|
"""Return current operation."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.hvac_mode
|
||||||
|
|
||||||
return self._hvac_mode
|
return self._hvac_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -765,6 +793,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
Need to be one of CURRENT_HVAC_*.
|
Need to be one of CURRENT_HVAC_*.
|
||||||
"""
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.hvac_action
|
||||||
|
|
||||||
if self._hvac_mode == HVAC_MODE_OFF:
|
if self._hvac_mode == HVAC_MODE_OFF:
|
||||||
return CURRENT_HVAC_OFF
|
return CURRENT_HVAC_OFF
|
||||||
if not self._is_device_active:
|
if not self._is_device_active:
|
||||||
@@ -781,12 +812,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.supported_features | self._support_flags
|
||||||
|
|
||||||
return self._support_flags
|
return self._support_flags
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _is_device_active(self):
|
def _is_device_active(self):
|
||||||
"""If the toggleable device is currently active."""
|
"""If the toggleable device is currently active."""
|
||||||
if not self.hass.states.get(self._heater_entity_id):
|
if self._is_over_climate or not self.hass.states.get(self._heater_entity_id):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.hass.states.is_state(self._heater_entity_id, STATE_ON)
|
return self.hass.states.is_state(self._heater_entity_id, STATE_ON)
|
||||||
@@ -799,6 +833,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
async def async_set_hvac_mode(self, hvac_mode):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
|
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
|
||||||
|
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
data = {ATTR_ENTITY_ID: self._climate_entity_id, "hvac_mode": hvac_mode}
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, data, context=self._context
|
||||||
|
)
|
||||||
|
# await self._underlying_climate.async_set_hvac_mode(hvac_mode)
|
||||||
|
self._hvac_mode = hvac_mode # self._underlying_climate.hvac_mode
|
||||||
|
else:
|
||||||
if hvac_mode == HVAC_MODE_HEAT:
|
if hvac_mode == HVAC_MODE_HEAT:
|
||||||
self._hvac_mode = HVAC_MODE_HEAT
|
self._hvac_mode = HVAC_MODE_HEAT
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
@@ -808,12 +851,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
elif hvac_mode == HVAC_MODE_OFF:
|
elif hvac_mode == HVAC_MODE_OFF:
|
||||||
self._hvac_mode = HVAC_MODE_OFF
|
self._hvac_mode = HVAC_MODE_OFF
|
||||||
if self._is_device_active:
|
if self._is_device_active:
|
||||||
await self._async_heater_turn_off()
|
await self._async_underlying_entity_turn_off()
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
|
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
|
||||||
return
|
return
|
||||||
# Ensure we update the current operation after changing the mode
|
# Ensure we update the current operation after changing the mode
|
||||||
|
self.update_custom_attributes()
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode):
|
async def async_set_preset_mode(self, preset_mode):
|
||||||
@@ -838,15 +882,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if preset_mode == PRESET_NONE:
|
if preset_mode == PRESET_NONE:
|
||||||
self._attr_preset_mode = PRESET_NONE
|
self._attr_preset_mode = PRESET_NONE
|
||||||
if self._saved_target_temp:
|
if self._saved_target_temp:
|
||||||
self._target_temp = self._saved_target_temp
|
await self._async_internal_set_temperature(self._saved_target_temp)
|
||||||
elif preset_mode == PRESET_ACTIVITY:
|
elif preset_mode == PRESET_ACTIVITY:
|
||||||
self._attr_preset_mode = PRESET_ACTIVITY
|
self._attr_preset_mode = PRESET_ACTIVITY
|
||||||
self._update_motion_temp()
|
await self._async_update_motion_temp()
|
||||||
else:
|
else:
|
||||||
if self._attr_preset_mode == PRESET_NONE:
|
if self._attr_preset_mode == PRESET_NONE:
|
||||||
self._saved_target_temp = self._target_temp
|
self._saved_target_temp = self._target_temp
|
||||||
self._attr_preset_mode = preset_mode
|
self._attr_preset_mode = preset_mode
|
||||||
self._target_temp = self.find_preset_temp(preset_mode)
|
await self._async_internal_set_temperature(
|
||||||
|
self.find_preset_temp(preset_mode)
|
||||||
|
)
|
||||||
|
|
||||||
self.save_preset_mode()
|
self.save_preset_mode()
|
||||||
|
|
||||||
@@ -878,6 +924,16 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if fan_mode is None:
|
if fan_mode is None:
|
||||||
return
|
return
|
||||||
self._fan_mode = fan_mode
|
self._fan_mode = fan_mode
|
||||||
|
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
data = {
|
||||||
|
ATTR_ENTITY_ID: self._climate_entity_id,
|
||||||
|
"fan_mode": fan_mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, data, context=self._context
|
||||||
|
)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_humidity(self, humidity: int):
|
async def async_set_humidity(self, humidity: int):
|
||||||
@@ -886,6 +942,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if humidity is None:
|
if humidity is None:
|
||||||
return
|
return
|
||||||
self._humidity = humidity
|
self._humidity = humidity
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
data = {
|
||||||
|
ATTR_ENTITY_ID: self._climate_entity_id,
|
||||||
|
"humidity": humidity,
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN, SERVICE_SET_HUMIDITY, data, context=self._context
|
||||||
|
)
|
||||||
|
|
||||||
async def async_set_swing_mode(self, swing_mode):
|
async def async_set_swing_mode(self, swing_mode):
|
||||||
"""Set new target swing operation."""
|
"""Set new target swing operation."""
|
||||||
@@ -893,6 +958,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if swing_mode is None:
|
if swing_mode is None:
|
||||||
return
|
return
|
||||||
self._swing_mode = swing_mode
|
self._swing_mode = swing_mode
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
data = {
|
||||||
|
ATTR_ENTITY_ID: self._climate_entity_id,
|
||||||
|
"swing_mode": swing_mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN, SERVICE_SET_SWING_MODE, data, context=self._context
|
||||||
|
)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
async def async_set_temperature(self, **kwargs):
|
||||||
@@ -901,11 +975,26 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
_LOGGER.info("%s - Set target temp: %s", self, temperature)
|
_LOGGER.info("%s - Set target temp: %s", self, temperature)
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
return
|
return
|
||||||
self._target_temp = temperature
|
await self._async_internal_set_temperature(temperature)
|
||||||
self._attr_preset_mode = PRESET_NONE
|
self._attr_preset_mode = PRESET_NONE
|
||||||
self.recalculate()
|
self.recalculate()
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
|
|
||||||
|
async def _async_internal_set_temperature(self, temperature):
|
||||||
|
"""Set the target temperature and the target temperature of underlying climate if any"""
|
||||||
|
self._target_temp = temperature
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
data = {
|
||||||
|
ATTR_ENTITY_ID: self._climate_entity_id,
|
||||||
|
"temperature": temperature,
|
||||||
|
"target_temp_high": self._attr_max_temp,
|
||||||
|
"target_temp_low": self._attr_min_temp,
|
||||||
|
}
|
||||||
|
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, data, context=self._context
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def entry_update_listener(
|
async def entry_update_listener(
|
||||||
self, _, config_entry: ConfigEntry # hass: HomeAssistant,
|
self, _, config_entry: ConfigEntry # hass: HomeAssistant,
|
||||||
@@ -978,8 +1067,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.debug("%s - Window delay condition is satisfied", self)
|
_LOGGER.debug("%s - Window delay condition is satisfied", self)
|
||||||
if not self._saved_hvac_mode:
|
# if not self._saved_hvac_mode:
|
||||||
self._saved_hvac_mode = self._hvac_mode
|
# self._saved_hvac_mode = self._hvac_mode
|
||||||
|
|
||||||
self._window_state = new_state.state
|
self._window_state = new_state.state
|
||||||
if self._window_state == STATE_OFF:
|
if self._window_state == STATE_OFF:
|
||||||
@@ -988,13 +1077,14 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self,
|
self,
|
||||||
self._saved_hvac_mode,
|
self._saved_hvac_mode,
|
||||||
)
|
)
|
||||||
await self.async_set_hvac_mode(self._saved_hvac_mode)
|
await self.restore_hvac_mode()
|
||||||
elif self._window_state == STATE_ON:
|
elif self._window_state == STATE_ON:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - Window is open. Set hvac_mode to '%s'", self, HVAC_MODE_OFF
|
"%s - Window is open. Set hvac_mode to '%s'", self, HVAC_MODE_OFF
|
||||||
)
|
)
|
||||||
self._saved_hvac_mode = self._hvac_mode
|
self.save_hvac_mode()
|
||||||
await self.async_set_hvac_mode(HVAC_MODE_OFF)
|
await self.async_set_hvac_mode(HVAC_MODE_OFF)
|
||||||
|
self.update_custom_attributes()
|
||||||
|
|
||||||
if self._window_call_cancel:
|
if self._window_call_cancel:
|
||||||
self._window_call_cancel()
|
self._window_call_cancel()
|
||||||
@@ -1050,7 +1140,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
new_preset,
|
new_preset,
|
||||||
)
|
)
|
||||||
# We do not change the preset which is kept to ACTIVITY but only the target_temperature
|
# We do not change the preset which is kept to ACTIVITY but only the target_temperature
|
||||||
self._target_temp = self._presets[new_preset]
|
await self._async_internal_set_temperature(self._presets[new_preset])
|
||||||
self.recalculate()
|
self.recalculate()
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
|
|
||||||
@@ -1070,7 +1160,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
|
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
|
||||||
self._heater_entity_id,
|
self._heater_entity_id,
|
||||||
)
|
)
|
||||||
await self._async_heater_turn_off()
|
await self._async_underlying_entity_turn_off()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_switch_changed(self, event):
|
def _async_switch_changed(self, event):
|
||||||
@@ -1083,6 +1173,27 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.hass.create_task(self._check_switch_initial_state())
|
self.hass.create_task(self._check_switch_initial_state())
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def _async_climate_changed(self, event):
|
||||||
|
"""Handle unerdlying climate state changes."""
|
||||||
|
new_state = event.data.get("new_state")
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - Underlying climate changed. Event.new_state is %s, hvac_mode=%s",
|
||||||
|
self,
|
||||||
|
new_state,
|
||||||
|
self._hvac_mode,
|
||||||
|
)
|
||||||
|
# old_state = event.data.get("old_state")
|
||||||
|
if new_state is None or new_state.state not in [
|
||||||
|
HVAC_MODE_COOL,
|
||||||
|
HVAC_MODE_HEAT,
|
||||||
|
HVAC_MODE_OFF,
|
||||||
|
]:
|
||||||
|
return
|
||||||
|
self._hvac_mode = new_state.state
|
||||||
|
self.update_custom_attributes()
|
||||||
|
await self._async_control_heating(True)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def _async_update_temp(self, state):
|
async def _async_update_temp(self, state):
|
||||||
"""Update thermostat with latest state from sensor."""
|
"""Update thermostat with latest state from sensor."""
|
||||||
@@ -1094,8 +1205,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._last_temperature_mesure = datetime.now()
|
self._last_temperature_mesure = datetime.now()
|
||||||
# try to restart if we were in security mode
|
# try to restart if we were in security mode
|
||||||
if self._security_state:
|
if self._security_state:
|
||||||
await self.async_set_hvac_mode(self._saved_hvac_mode)
|
await self.check_security()
|
||||||
await self.restore_preset_mode()
|
|
||||||
|
|
||||||
except ValueError as ex:
|
except ValueError as ex:
|
||||||
_LOGGER.error("Unable to update temperature from sensor: %s", ex)
|
_LOGGER.error("Unable to update temperature from sensor: %s", ex)
|
||||||
@@ -1111,8 +1221,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._last_ext_temperature_mesure = datetime.now()
|
self._last_ext_temperature_mesure = datetime.now()
|
||||||
# try to restart if we were in security mode
|
# try to restart if we were in security mode
|
||||||
if self._security_state:
|
if self._security_state:
|
||||||
await self.async_set_hvac_mode(self._saved_hvac_mode)
|
await self.check_security()
|
||||||
await self.restore_preset_mode()
|
|
||||||
except ValueError as ex:
|
except ValueError as ex:
|
||||||
_LOGGER.error("Unable to update external temperature from sensor: %s", ex)
|
_LOGGER.error("Unable to update external temperature from sensor: %s", ex)
|
||||||
|
|
||||||
@@ -1181,10 +1290,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if new_state is None:
|
if new_state is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._update_presence(new_state.state)
|
await self._async_update_presence(new_state.state)
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
|
|
||||||
def _update_presence(self, new_state):
|
async def _async_update_presence(self, new_state):
|
||||||
_LOGGER.debug("%s - Updating presence. New state is %s", self, new_state)
|
_LOGGER.debug("%s - Updating presence. New state is %s", self, new_state)
|
||||||
self._presence_state = new_state
|
self._presence_state = new_state
|
||||||
if self._attr_preset_mode in HIDDEN_PRESETS or self._presence_on is False:
|
if self._attr_preset_mode in HIDDEN_PRESETS or self._presence_on is False:
|
||||||
@@ -1228,10 +1337,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self,
|
self,
|
||||||
new_temp,
|
new_temp,
|
||||||
)
|
)
|
||||||
self._target_temp = new_temp
|
await self._async_internal_set_temperature(new_temp)
|
||||||
self.recalculate()
|
self.recalculate()
|
||||||
|
|
||||||
def _update_motion_temp(self):
|
async def _async_update_motion_temp(self):
|
||||||
"""Update the temperature considering the ACTIVITY preset and current motion state"""
|
"""Update the temperature considering the ACTIVITY preset and current motion state"""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - Calling _update_motion_temp preset_mode=%s, motion_state=%s",
|
"%s - Calling _update_motion_temp preset_mode=%s, motion_state=%s",
|
||||||
@@ -1245,11 +1354,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
self._target_temp = self._presets[
|
await self._async_internal_set_temperature(
|
||||||
|
self._presets[
|
||||||
self._motion_preset
|
self._motion_preset
|
||||||
if self._motion_state == STATE_ON
|
if self._motion_state == STATE_ON
|
||||||
else self._no_motion_preset
|
else self._no_motion_preset
|
||||||
]
|
]
|
||||||
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - regarding motion, target_temp have been set to %.2f",
|
"%s - regarding motion, target_temp have been set to %.2f",
|
||||||
self,
|
self,
|
||||||
@@ -1263,12 +1374,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
HA_DOMAIN, SERVICE_TURN_ON, data, context=self._context
|
HA_DOMAIN, SERVICE_TURN_ON, data, context=self._context
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_heater_turn_off(self):
|
async def _async_underlying_entity_turn_off(self):
|
||||||
"""Turn heater toggleable device off."""
|
"""Turn heater toggleable device off."""
|
||||||
|
if not self._is_over_climate:
|
||||||
data = {ATTR_ENTITY_ID: self._heater_entity_id}
|
data = {ATTR_ENTITY_ID: self._heater_entity_id}
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
|
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
data = {ATTR_ENTITY_ID: self._climate_entity_id}
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
|
||||||
|
)
|
||||||
|
|
||||||
def save_preset_mode(self):
|
def save_preset_mode(self):
|
||||||
"""Save the current preset mode to be restored later
|
"""Save the current preset mode to be restored later
|
||||||
@@ -1290,6 +1407,26 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
):
|
):
|
||||||
await self._async_set_preset_mode_internal(self._saved_preset_mode)
|
await self._async_set_preset_mode_internal(self._saved_preset_mode)
|
||||||
|
|
||||||
|
def save_hvac_mode(self):
|
||||||
|
"""Save the current hvac-mode to be restored later"""
|
||||||
|
self._saved_hvac_mode = self._hvac_mode
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Saved hvac mode - saved_hvac_mode is %s, hvac_mode is %s",
|
||||||
|
self,
|
||||||
|
self._saved_hvac_mode,
|
||||||
|
self._hvac_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def restore_hvac_mode(self):
|
||||||
|
"""Restore a previous hvac_mod"""
|
||||||
|
await self.async_set_hvac_mode(self._saved_hvac_mode)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Restored hvac_mode - saved_hvac_mode is %s, hvac_mode is %s",
|
||||||
|
self,
|
||||||
|
self._saved_hvac_mode,
|
||||||
|
self._hvac_mode,
|
||||||
|
)
|
||||||
|
|
||||||
async def check_overpowering(self) -> bool:
|
async def check_overpowering(self) -> bool:
|
||||||
"""Check the overpowering condition
|
"""Check the overpowering condition
|
||||||
Turn the preset_mode of the heater to 'power' if power conditions are exceeded
|
Turn the preset_mode of the heater to 'power' if power conditions are exceeded
|
||||||
@@ -1305,62 +1442,144 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._current_power_max,
|
self._current_power_max,
|
||||||
self._device_power,
|
self._device_power,
|
||||||
)
|
)
|
||||||
self._overpowering_state = (
|
ret = self._current_power + self._device_power >= self._current_power_max
|
||||||
self._current_power + self._device_power >= self._current_power_max
|
if (
|
||||||
)
|
not self._overpowering_state
|
||||||
if self._overpowering_state:
|
and ret
|
||||||
|
and not self._hvac_mode == HVAC_MODE_OFF
|
||||||
|
):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - overpowering is detected. Heater preset will be set to 'power'",
|
"%s - overpowering is detected. Heater preset will be set to 'power'",
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
|
if self._is_over_climate:
|
||||||
|
self.save_hvac_mode()
|
||||||
|
self.save_preset_mode()
|
||||||
|
await self._async_underlying_entity_turn_off()
|
||||||
await self._async_set_preset_mode_internal(PRESET_POWER)
|
await self._async_set_preset_mode_internal(PRESET_POWER)
|
||||||
return self._overpowering_state
|
|
||||||
|
|
||||||
# Check if we need to remove the POWER preset
|
# Check if we need to remove the POWER preset
|
||||||
if self._attr_preset_mode == PRESET_POWER and not self._overpowering_state:
|
if (
|
||||||
|
self._overpowering_state
|
||||||
|
and not ret
|
||||||
|
and self._attr_preset_mode == PRESET_POWER
|
||||||
|
):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - end of overpowering is detected. Heater preset will be restored to '%s'",
|
"%s - end of overpowering is detected. Heater preset will be restored to '%s'",
|
||||||
self,
|
self,
|
||||||
self._saved_preset_mode,
|
self._saved_preset_mode,
|
||||||
)
|
)
|
||||||
|
if self._is_over_climate:
|
||||||
|
await self.restore_hvac_mode()
|
||||||
await self.restore_preset_mode()
|
await self.restore_preset_mode()
|
||||||
|
|
||||||
def check_date_temperature(self) -> bool:
|
self._overpowering_state = ret
|
||||||
|
return self._overpowering_state
|
||||||
|
|
||||||
|
async def check_security(self) -> bool:
|
||||||
"""Check if last temperature date is too long"""
|
"""Check if last temperature date is too long"""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
delta_temp = (now - self._last_temperature_mesure).total_seconds() / 60.0
|
delta_temp = (now - self._last_temperature_mesure).total_seconds() / 60.0
|
||||||
delta_ext_temp = (
|
delta_ext_temp = (
|
||||||
now - self._last_ext_temperature_mesure
|
now - self._last_ext_temperature_mesure
|
||||||
).total_seconds() / 60.0
|
).total_seconds() / 60.0
|
||||||
if (
|
_LOGGER.debug(
|
||||||
|
"%s - checking security delta_temp=%.1f delta_ext_temp=%.1f",
|
||||||
|
self,
|
||||||
|
delta_temp,
|
||||||
|
delta_ext_temp,
|
||||||
|
)
|
||||||
|
|
||||||
|
temp_cond: bool = (
|
||||||
delta_temp > self._security_delay_min
|
delta_temp > self._security_delay_min
|
||||||
or delta_ext_temp > self._security_delay_min
|
or delta_ext_temp > self._security_delay_min
|
||||||
):
|
)
|
||||||
|
climate_cond: bool = self._is_over_climate and self.hvac_action not in [
|
||||||
|
CURRENT_HVAC_COOL,
|
||||||
|
CURRENT_HVAC_IDLE,
|
||||||
|
]
|
||||||
|
switch_cond: bool = (
|
||||||
|
not self._is_over_climate
|
||||||
|
and self._prop_algorithm is not None
|
||||||
|
and self._prop_algorithm.on_percent > 0.75
|
||||||
|
)
|
||||||
|
|
||||||
|
ret = False
|
||||||
|
if temp_cond and climate_cond:
|
||||||
|
if not self._security_state:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f)",
|
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and underlying climate is %s. Set it into security mode",
|
||||||
self,
|
self,
|
||||||
self._security_delay_min,
|
self._security_delay_min,
|
||||||
delta_temp,
|
delta_temp,
|
||||||
delta_ext_temp,
|
delta_ext_temp,
|
||||||
|
self.hvac_action,
|
||||||
)
|
)
|
||||||
return False
|
ret = True
|
||||||
|
|
||||||
return True
|
if temp_cond and switch_cond:
|
||||||
|
if not self._security_state:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent is high (%.2f). Set it into security mode",
|
||||||
|
self,
|
||||||
|
self._security_delay_min,
|
||||||
|
delta_temp,
|
||||||
|
delta_ext_temp,
|
||||||
|
self._prop_algorithm.on_percent,
|
||||||
|
)
|
||||||
|
ret = True
|
||||||
|
|
||||||
|
if not self._security_state and ret:
|
||||||
|
self._security_state = ret
|
||||||
|
self.save_hvac_mode()
|
||||||
|
self.save_preset_mode()
|
||||||
|
await self._async_set_preset_mode_internal(PRESET_SECURITY)
|
||||||
|
await self.async_set_hvac_mode(HVAC_MODE_OFF)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._security_state
|
||||||
|
and self._attr_preset_mode == PRESET_SECURITY
|
||||||
|
and not ret
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s - End of security mode. restoring hvac_mode to %s and preset_mode to %s",
|
||||||
|
self,
|
||||||
|
self._saved_hvac_mode,
|
||||||
|
self._saved_preset_mode,
|
||||||
|
)
|
||||||
|
self._security_state = ret
|
||||||
|
await self.restore_hvac_mode()
|
||||||
|
await self.restore_preset_mode()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
async def _async_control_heating(self, force=False, _=None):
|
async def _async_control_heating(self, force=False, _=None):
|
||||||
"""The main function used to run the calculation at each cycle"""
|
"""The main function used to run the calculation at each cycle"""
|
||||||
|
|
||||||
overpowering: bool = await self.check_overpowering()
|
_LOGGER.info(
|
||||||
if overpowering:
|
"%s - Checking new cycle. hvac_mode=%s, security_state=%s, preset_mode=%s",
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - The max power is exceeded. Heater will not be started. preset_mode is now '%s'", # pylint: disable=line-too-long
|
|
||||||
self,
|
self,
|
||||||
|
self._hvac_mode,
|
||||||
|
self._security_state,
|
||||||
self._attr_preset_mode,
|
self._attr_preset_mode,
|
||||||
)
|
)
|
||||||
await self._async_heater_turn_off()
|
|
||||||
|
# Check overpowering condition
|
||||||
|
overpowering: bool = await self.check_overpowering()
|
||||||
|
if overpowering:
|
||||||
_LOGGER.debug("%s - End of cycle (0)", self)
|
_LOGGER.debug("%s - End of cycle (0)", self)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
security: bool = await self.check_security()
|
||||||
|
if security:
|
||||||
|
_LOGGER.debug("%s - End of cycle (1)", self)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Stop here if we are off
|
||||||
|
if self._hvac_mode == HVAC_MODE_OFF:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._is_over_climate:
|
||||||
on_time_sec: int = self._prop_algorithm.on_time_sec
|
on_time_sec: int = self._prop_algorithm.on_time_sec
|
||||||
off_time_sec: int = self._prop_algorithm.off_time_sec
|
off_time_sec: int = self._prop_algorithm.off_time_sec
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -1384,7 +1603,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
self._should_relaunch_control_heating = True
|
self._should_relaunch_control_heating = True
|
||||||
_LOGGER.debug("%s - End of cycle (1)", self)
|
_LOGGER.debug("%s - End of cycle (2)", self)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._hvac_mode == HVAC_MODE_HEAT and on_time_sec > 0:
|
if self._hvac_mode == HVAC_MODE_HEAT and on_time_sec > 0:
|
||||||
@@ -1397,19 +1616,14 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._async_cancel_cycle = None
|
self._async_cancel_cycle = None
|
||||||
_LOGGER.debug("%s - Stopping cycle during calculation", self)
|
_LOGGER.debug("%s - Stopping cycle during calculation", self)
|
||||||
|
|
||||||
check_dates = self.check_date_temperature()
|
if on:
|
||||||
if time > 0 and on is True and check_dates is False:
|
security = (
|
||||||
_LOGGER.warning("%s - Set the thermostat into security mode", self)
|
await self.check_security()
|
||||||
self._security_state = True
|
or await self.check_overpowering()
|
||||||
self._saved_hvac_mode = self.hvac_mode
|
)
|
||||||
self.save_preset_mode()
|
if security:
|
||||||
await self._async_set_preset_mode_internal(PRESET_SECURITY)
|
_LOGGER.debug("%s - End of cycle (3)", self)
|
||||||
await self.async_set_hvac_mode(HVAC_MODE_OFF)
|
|
||||||
# The cycle is not restarted in security mode. It will be restarted by a condition changes
|
|
||||||
_LOGGER.debug("%s - End of cycle (2)", self)
|
|
||||||
return
|
return
|
||||||
if check_dates:
|
|
||||||
self._security_state = False
|
|
||||||
|
|
||||||
action_label = "start" if on else "stop"
|
action_label = "start" if on else "stop"
|
||||||
if self._should_relaunch_control_heating:
|
if self._should_relaunch_control_heating:
|
||||||
@@ -1432,7 +1646,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
await heater_action()
|
await heater_action()
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("%s - No action on heater cause duration is 0", self)
|
_LOGGER.debug(
|
||||||
|
"%s - No action on heater cause duration is 0", self
|
||||||
|
)
|
||||||
self.update_custom_attributes()
|
self.update_custom_attributes()
|
||||||
self._async_cancel_cycle = async_call_later(
|
self._async_cancel_cycle = async_call_later(
|
||||||
self.hass,
|
self.hass,
|
||||||
@@ -1452,7 +1668,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await _turn_on_off_later(
|
await _turn_on_off_later(
|
||||||
on=False,
|
on=False,
|
||||||
time=self._prop_algorithm.off_time_sec,
|
time=self._prop_algorithm.off_time_sec,
|
||||||
heater_action=self._async_heater_turn_off,
|
heater_action=self._async_underlying_entity_turn_off,
|
||||||
next_cycle_action=_turn_on_later,
|
next_cycle_action=_turn_on_later,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1465,7 +1681,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
off_time_sec // 60,
|
off_time_sec // 60,
|
||||||
off_time_sec % 60,
|
off_time_sec % 60,
|
||||||
)
|
)
|
||||||
await self._async_heater_turn_off()
|
await self._async_underlying_entity_turn_off()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("%s - nothing to do", self)
|
_LOGGER.debug("%s - nothing to do", self)
|
||||||
@@ -1476,6 +1692,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"""A utility function to force the calculation of a the algo and
|
"""A utility function to force the calculation of a the algo and
|
||||||
update the custom attributes and write the state
|
update the custom attributes and write the state
|
||||||
"""
|
"""
|
||||||
|
if self._is_over_climate:
|
||||||
|
self.update_custom_attributes()
|
||||||
|
return
|
||||||
|
|
||||||
_LOGGER.debug("%s - recalculate all", self)
|
_LOGGER.debug("%s - recalculate all", self)
|
||||||
self._prop_algorithm.calculate(
|
self._prop_algorithm.calculate(
|
||||||
self._target_temp, self._cur_temp, self._cur_ext_temp
|
self._target_temp, self._cur_temp, self._cur_ext_temp
|
||||||
@@ -1486,28 +1706,25 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
def update_custom_attributes(self):
|
def update_custom_attributes(self):
|
||||||
"""Update the custom extra attributes for the entity"""
|
"""Update the custom extra attributes for the entity"""
|
||||||
|
|
||||||
self._attr_extra_state_attributes = {
|
self._attr_extra_state_attributes: dict(str, str) = {
|
||||||
|
"hvac_mode": self._hvac_mode,
|
||||||
|
"type": self._thermostat_type,
|
||||||
"eco_temp": self._presets[PRESET_ECO],
|
"eco_temp": self._presets[PRESET_ECO],
|
||||||
"boost_temp": self._presets[PRESET_BOOST],
|
"boost_temp": self._presets[PRESET_BOOST],
|
||||||
"comfort_temp": self._presets[PRESET_COMFORT],
|
"comfort_temp": self._presets[PRESET_COMFORT],
|
||||||
"eco_away_temp": self._presets_away[self.get_preset_away_name(PRESET_ECO)],
|
"eco_away_temp": self._presets_away.get(
|
||||||
"boost_away_temp": self._presets_away[
|
self.get_preset_away_name(PRESET_ECO)
|
||||||
|
),
|
||||||
|
"boost_away_temp": self._presets_away.get(
|
||||||
self.get_preset_away_name(PRESET_BOOST)
|
self.get_preset_away_name(PRESET_BOOST)
|
||||||
],
|
),
|
||||||
"comfort_away_temp": self._presets_away[
|
"comfort_away_temp": self._presets_away.get(
|
||||||
self.get_preset_away_name(PRESET_COMFORT)
|
self.get_preset_away_name(PRESET_COMFORT)
|
||||||
],
|
),
|
||||||
"power_temp": self._power_temp,
|
"power_temp": self._power_temp,
|
||||||
"on_percent": self._prop_algorithm.on_percent,
|
|
||||||
"on_time_sec": self._prop_algorithm.on_time_sec,
|
|
||||||
"off_time_sec": self._prop_algorithm.off_time_sec,
|
|
||||||
"ext_current_temperature": self._cur_ext_temp,
|
"ext_current_temperature": self._cur_ext_temp,
|
||||||
"current_power": self._current_power,
|
"current_power": self._current_power,
|
||||||
"current_power_max": self._current_power_max,
|
"current_power_max": self._current_power_max,
|
||||||
"cycle_min": self._cycle_min,
|
|
||||||
"function": self._proportional_function,
|
|
||||||
"tpi_coef_int": self._tpi_coef_int,
|
|
||||||
"tpi_coef_ext": self._tpi_coef_ext,
|
|
||||||
"saved_preset_mode": self._saved_preset_mode,
|
"saved_preset_mode": self._saved_preset_mode,
|
||||||
"saved_target_temp": self._saved_target_temp,
|
"saved_target_temp": self._saved_target_temp,
|
||||||
"saved_hvac_mode": self._saved_hvac_mode,
|
"saved_hvac_mode": self._saved_hvac_mode,
|
||||||
@@ -1522,6 +1739,28 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"minimal_activation_delay_sec": self._minimal_activation_delay,
|
"minimal_activation_delay_sec": self._minimal_activation_delay,
|
||||||
"last_update_datetime": datetime.now().isoformat(),
|
"last_update_datetime": datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
|
if self._is_over_climate:
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"underlying_climate"
|
||||||
|
] = self._climate_entity_id
|
||||||
|
else:
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"underlying_switch"
|
||||||
|
] = self._heater_entity_id
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"on_percent"
|
||||||
|
] = self._prop_algorithm.on_percent
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"on_time_sec"
|
||||||
|
] = self._prop_algorithm.on_time_sec
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"off_time_sec"
|
||||||
|
] = self._prop_algorithm.off_time_sec
|
||||||
|
self._attr_extra_state_attributes["cycle_min"] = self._cycle_min
|
||||||
|
self._attr_extra_state_attributes["function"] = self._proportional_function
|
||||||
|
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
|
||||||
|
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - Calling update_custom_attributes: %s",
|
"%s - Calling update_custom_attributes: %s",
|
||||||
@@ -1534,7 +1773,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"""update the entity if the config entry have been updated
|
"""update the entity if the config entry have been updated
|
||||||
Note: this don't work either
|
Note: this don't work either
|
||||||
"""
|
"""
|
||||||
_LOGGER.info("%s - The config entry have been updated.")
|
_LOGGER.info("%s - The config entry have been updated")
|
||||||
|
|
||||||
async def service_set_presence(self, presence):
|
async def service_set_presence(self, presence):
|
||||||
"""Called by a service call:
|
"""Called by a service call:
|
||||||
@@ -1545,7 +1784,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
entity_id: climate.thermostat_1
|
entity_id: climate.thermostat_1
|
||||||
"""
|
"""
|
||||||
_LOGGER.info("%s - Calling service_set_presence, presence: %s", self, presence)
|
_LOGGER.info("%s - Calling service_set_presence, presence: %s", self, presence)
|
||||||
self._update_presence(presence)
|
await self._async_update_presence(presence)
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
|
|
||||||
async def service_set_preset_temperature(
|
async def service_set_preset_temperature(
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import copy
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.const import TEMPERATURE, UnitOfPower
|
||||||
|
from homeassistant.util.unit_system import TEMPERATURE_UNITS
|
||||||
|
|
||||||
from homeassistant.core import callback, async_get_hass
|
from homeassistant.core import callback, async_get_hass
|
||||||
from homeassistant.config_entries import (
|
from homeassistant.config_entries import (
|
||||||
@@ -17,27 +20,27 @@ from homeassistant.config_entries import (
|
|||||||
|
|
||||||
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_component import EntityComponent
|
||||||
from homeassistant.helpers.entity_registry import EntityRegistry, async_get
|
from homeassistant.helpers.entity_registry import (
|
||||||
from homeassistant.components.climate import ClimateEntity
|
RegistryEntry,
|
||||||
|
async_get,
|
||||||
|
)
|
||||||
from homeassistant.components.climate.const import DOMAIN as CLIMATE_DOMAIN
|
from homeassistant.components.climate.const import DOMAIN as CLIMATE_DOMAIN
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.climate import ClimateEntity
|
||||||
from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN
|
||||||
from homeassistant.components.input_boolean import (
|
from homeassistant.components.input_boolean import (
|
||||||
InputBoolean,
|
|
||||||
DOMAIN as INPUT_BOOLEAN_DOMAIN,
|
DOMAIN as INPUT_BOOLEAN_DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
|
||||||
from homeassistant.components.sensor.const import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor.const import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.components.input_number import (
|
from homeassistant.components.input_number import (
|
||||||
InputNumber,
|
|
||||||
DOMAIN as INPUT_NUMBER_DOMAIN,
|
DOMAIN as INPUT_NUMBER_DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.person import DOMAIN as PERSON_DOMAIN
|
||||||
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@@ -76,6 +79,7 @@ from .const import (
|
|||||||
CONF_USE_PRESENCE_FEATURE,
|
CONF_USE_PRESENCE_FEATURE,
|
||||||
CONF_USE_POWER_FEATURE,
|
CONF_USE_POWER_FEATURE,
|
||||||
CONF_THERMOSTAT_TYPES,
|
CONF_THERMOSTAT_TYPES,
|
||||||
|
UnknownEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -122,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."""
|
||||||
|
|
||||||
@@ -135,22 +170,43 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
self.hass = async_get_hass()
|
self.hass = async_get_hass()
|
||||||
ent_reg = async_get(hass=self.hass)
|
ent_reg = async_get(hass=self.hass)
|
||||||
|
|
||||||
climates = [] # self.find_all_climates()
|
climates = []
|
||||||
switches = [] # self.find_all_heaters()
|
switches = []
|
||||||
temp_sensors = [] # self.find_all_temperature_sensors()
|
temp_sensors = []
|
||||||
|
power_sensors = []
|
||||||
|
window_sensors = []
|
||||||
|
presence_sensors = []
|
||||||
|
|
||||||
k: str
|
k: str
|
||||||
for k in ent_reg.entities:
|
for k in ent_reg.entities:
|
||||||
v = ent_reg.entities[k]
|
v: RegistryEntry = ent_reg.entities[k]
|
||||||
if k.startswith(CLIMATE_DOMAIN):
|
_LOGGER.debug("Looking entity: %s", k)
|
||||||
climates.append(k)
|
# if k.startswith(CLIMATE_DOMAIN) and (
|
||||||
elif k.startswith(SWITCH_DOMAIN) or k.startswith(INPUT_BOOLEAN_DOMAIN):
|
# 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)
|
switches.append(k)
|
||||||
elif k.startswith(INPUT_NUMBER_DOMAIN):
|
elif is_temperature_sensor(v):
|
||||||
temp_sensors.append(k)
|
_LOGGER.debug("Temperature sensor !")
|
||||||
elif k.startswith(SENSOR_DOMAIN):
|
|
||||||
_LOGGER.debug("We have found sensor: %s", v)
|
|
||||||
temp_sensors.append(k)
|
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
|
||||||
|
if k.startswith(INPUT_BOOLEAN_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(
|
||||||
{
|
{
|
||||||
@@ -160,6 +216,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
): vol.In(CONF_THERMOSTAT_TYPES),
|
): vol.In(CONF_THERMOSTAT_TYPES),
|
||||||
vol.Required(CONF_TEMP_SENSOR): vol.In(temp_sensors),
|
vol.Required(CONF_TEMP_SENSOR): vol.In(temp_sensors),
|
||||||
vol.Required(CONF_EXTERNAL_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_TEMP_MIN, default=7): vol.Coerce(float),
|
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float),
|
||||||
vol.Required(CONF_TEMP_MAX, default=35): 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_WINDOW_FEATURE, default=False): cv.boolean,
|
||||||
@@ -179,7 +236,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
PROPORTIONAL_FUNCTION_TPI,
|
PROPORTIONAL_FUNCTION_TPI,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -205,14 +261,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
|
||||||
@@ -225,16 +281,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(
|
||||||
{
|
{
|
||||||
@@ -455,30 +511,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
_LOGGER.debug("Found all climate entities: %s", ret)
|
_LOGGER.debug("Found all climate entities: %s", ret)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def find_all_heaters(self) -> list(str):
|
|
||||||
"""Find all heater known by HA"""
|
|
||||||
component: EntityComponent[SwitchEntity] = self.hass.data[SWITCH_DOMAIN]
|
|
||||||
ret: list(str) = list()
|
|
||||||
for entity in component.entities:
|
|
||||||
ret.append(entity.entity_id)
|
|
||||||
# component = self.hass.data[INPUT_BOOLEAN_DOMAIN]
|
|
||||||
# for entity in component.entities:
|
|
||||||
# ret.append(entity.entity_id)
|
|
||||||
_LOGGER.debug("Found all switch entities: %s", ret)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def find_all_temperature_sensors(self) -> list(str):
|
|
||||||
"""Find all heater known by HA"""
|
|
||||||
component: EntityComponent[SensorEntity] = self.hass.data[SENSOR_DOMAIN]
|
|
||||||
ret: list(str) = list()
|
|
||||||
for entity in component.entities:
|
|
||||||
ret.append(entity.entity_id)
|
|
||||||
# component = self.hass.data[INPUT_NUMBER_DOMAIN]
|
|
||||||
# for entity in component.entities:
|
|
||||||
# ret.append(entity.entity_id)
|
|
||||||
_LOGGER.debug("Found all temperature sensore entities: %s", ret)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class VersatileThermostatConfigFlow(
|
class VersatileThermostatConfigFlow(
|
||||||
VersatileThermostatBaseConfigFlow, HAConfigFlow, domain=DOMAIN
|
VersatileThermostatBaseConfigFlow, HAConfigFlow, domain=DOMAIN
|
||||||
@@ -502,16 +534,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
|
||||||
@@ -537,7 +565,25 @@ 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:
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
@@ -123,3 +125,7 @@ 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."""
|
||||||
|
|||||||
@@ -9,10 +9,9 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"thermostat_type": "Thermostat type",
|
"thermostat_type": "Thermostat type",
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat",
|
|
||||||
"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)",
|
||||||
"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_window_feature": "Use window detection",
|
||||||
@@ -22,10 +21,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
"title": "Linked entity",
|
||||||
|
"description": "Linked entity attributes",
|
||||||
|
"data": {
|
||||||
"heater_entity_id": "Heater entity id",
|
"heater_entity_id": "Heater entity id",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
|
||||||
"climate_entity_id": "Underlying thermostat entity id"
|
"climate_entity_id": "Underlying thermostat entity id"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
"title": "TPI",
|
"title": "TPI",
|
||||||
@@ -91,6 +93,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selectors": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat over a switch",
|
||||||
|
"thermostat_over_climate": "Thermostat over another thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
@@ -108,10 +118,9 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"thermostat_type": "Thermostat type",
|
"thermostat_type": "Thermostat type",
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat",
|
|
||||||
"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)",
|
||||||
"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_window_feature": "Use window detection",
|
||||||
@@ -121,10 +130,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
"title": "Linked entity",
|
||||||
|
"description": "Linked entity attributes",
|
||||||
|
"data": {
|
||||||
"heater_entity_id": "Heater entity id",
|
"heater_entity_id": "Heater entity id",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
|
||||||
"climate_entity_id": "Underlying thermostat entity id"
|
"climate_entity_id": "Underlying thermostat entity id"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
"title": "TPI",
|
"title": "TPI",
|
||||||
@@ -190,6 +202,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selectors": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat over a switch",
|
||||||
|
"thermostat_over_climate": "Thermostat over another thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
|
|||||||
@@ -9,10 +9,9 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"thermostat_type": "Thermostat type",
|
"thermostat_type": "Thermostat type",
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat",
|
|
||||||
"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)",
|
||||||
"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_window_feature": "Use window detection",
|
||||||
@@ -22,10 +21,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
"title": "Linked entity",
|
||||||
|
"description": "Linked entity attributes",
|
||||||
|
"data": {
|
||||||
"heater_entity_id": "Heater entity id",
|
"heater_entity_id": "Heater entity id",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
|
||||||
"climate_entity_id": "Underlying thermostat entity id"
|
"climate_entity_id": "Underlying thermostat entity id"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
"title": "TPI",
|
"title": "TPI",
|
||||||
@@ -91,6 +93,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selectors": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat over a switch",
|
||||||
|
"thermostat_over_climate": "Thermostat over another thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
@@ -108,10 +118,9 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"thermostat_type": "Thermostat type",
|
"thermostat_type": "Thermostat type",
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat",
|
|
||||||
"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)",
|
||||||
"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_window_feature": "Use window detection",
|
||||||
@@ -121,10 +130,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
"title": "Linked entity",
|
||||||
|
"description": "Linked entity attributes",
|
||||||
|
"data": {
|
||||||
"heater_entity_id": "Heater entity id",
|
"heater_entity_id": "Heater entity id",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
|
||||||
"climate_entity_id": "Underlying thermostat entity id"
|
"climate_entity_id": "Underlying thermostat entity id"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
"title": "TPI",
|
"title": "TPI",
|
||||||
@@ -190,6 +202,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selectors": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat over a switch",
|
||||||
|
"thermostat_over_climate": "Thermostat over another thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
"description": "Principaux attributs obligatoires",
|
"description": "Principaux attributs obligatoires",
|
||||||
"data": {
|
"data": {
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"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)",
|
||||||
@@ -22,10 +20,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
"title": "Entité liée",
|
||||||
|
"description": "Attributs de l'entité liée",
|
||||||
|
"data": {
|
||||||
"heater_entity_id": "Radiateur entity id",
|
"heater_entity_id": "Radiateur entity id",
|
||||||
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
|
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
||||||
"cycle_min": "Durée du cycle (minutes)",
|
|
||||||
"climate_entity_id": "Thermostat sous-jacent entity id"
|
"climate_entity_id": "Thermostat sous-jacent entity id"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
"title": "TPI",
|
"title": "TPI",
|
||||||
@@ -91,6 +92,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selectors": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat sur un switch",
|
||||||
|
"thermostat_over_climate": "Thermostat sur un autre thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Erreur inattendue",
|
"unknown": "Erreur inattendue",
|
||||||
"unknown_entity": "entity id inconnu"
|
"unknown_entity": "entity id inconnu"
|
||||||
@@ -121,10 +130,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
|
"title": "Entité liée",
|
||||||
|
"description": "Attributs de l'entité liée",
|
||||||
|
"data": {
|
||||||
"heater_entity_id": "Radiateur entity id",
|
"heater_entity_id": "Radiateur entity id",
|
||||||
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
|
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
||||||
"cycle_min": "Durée du cycle (minutes)",
|
|
||||||
"climate_entity_id": "Thermostat sous-jacent entity id"
|
"climate_entity_id": "Thermostat sous-jacent entity id"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
"title": "TPI",
|
"title": "TPI",
|
||||||
@@ -190,6 +202,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selectors": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat sur un switch",
|
||||||
|
"thermostat_over_climate": "Thermostat sur un autre thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Erreur inattendue",
|
"unknown": "Erreur inattendue",
|
||||||
"unknown_entity": "entity id inconnu"
|
"unknown_entity": "entity id inconnu"
|
||||||
|
|||||||
Reference in New Issue
Block a user