Compare commits
10 Commits
1.1.0
...
2.0.0.beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d977c4981 | ||
|
|
115df52f67 | ||
|
|
3371197594 | ||
|
|
7e63c9aa79 | ||
|
|
100cfaeac9 | ||
|
|
77631e94d9 | ||
|
|
49c85eeb2b | ||
|
|
bb2c1d328b | ||
|
|
b607fb1075 | ||
|
|
4786949aeb |
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.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|||||||
@@ -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,25 @@
|
|||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"versatile_thermostat": {
|
||||||
|
"states_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"power": "Shedding",
|
||||||
|
"security": "Security"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,25 @@
|
|||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"versatile_thermostat": {
|
||||||
|
"states_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"power": "Shedding",
|
||||||
|
"security": "Security"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,25 @@
|
|||||||
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"versatile_thermostat": {
|
||||||
|
"states_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"power": "Délestage",
|
||||||
|
"security": "Sécurité"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user