Compare commits

...

8 Commits

Author SHA1 Message Date
Jean-Marc Collin f970c18eaf Issue #100: compatibility with HA 2023.9.0 2023-09-08 08:48:09 +02:00
Jean-Marc Collin af51ef62e0 Issue #99 : over climate VTherm a regulated by the device itself and should not goes into security 2023-08-30 09:06:26 +02:00
Jean-Marc Collin b38fbd9d78 Issue #99 - security mode toggling 100 times within 2 minutes 2023-08-27 18:06:43 +02:00
Jean-Marc Collin 6e8e72e343 FIX Service name Github error 2023-08-16 22:30:46 +02:00
Jean-Marc Collin 2bebe3e210 Issue #95 - the integration would switch ac on and off rapidly and lock up home assistant if outside temp is NaN 2023-08-05 20:02:54 +02:00
Jean-Marc Collin aa3b87762d FIX AC climate stops even if already stopped 2023-07-30 21:25:20 +02:00
Jean-Marc Collin f4cabbf2c0 FIX unit tests 2023-07-30 18:09:42 +02:00
Jean-Marc Collin 24b59e545b FIX cancel_timer await 2023-07-29 11:22:50 +02:00
9 changed files with 335 additions and 323 deletions
+175 -175
View File
@@ -1,196 +1,196 @@
default_config: default_config:
logger: logger:
default: info default: info
logs: logs:
custom_components.versatile_thermostat: info custom_components.versatile_thermostat: info
custom_components.versatile_thermostat.underlyings: debug custom_components.versatile_thermostat.underlyings: debug
custom_components.versatile_thermostat.climate: debug custom_components.versatile_thermostat.climate: debug
# 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 start: true
wait: false wait: false
port: 5678 port: 5678
input_number: input_number:
fake_temperature_sensor1: fake_temperature_sensor1:
name: Temperature name: Temperature
min: 0 min: 0
max: 35 max: 35
step: .1 step: .1
icon: mdi:thermometer icon: mdi:thermometer
unit_of_measurement: °C unit_of_measurement: °C
mode: box mode: box
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 unit_of_measurement: °C
mode: box mode: box
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 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 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.
fake_window_sensor1: fake_window_sensor1:
name: Window 1 name: Window 1
icon: mdi:window-closed-variant icon: mdi:window-closed-variant
# input_boolean to simulate the heater entity switch. Only for development environment. # input_boolean to simulate the heater entity switch. Only for development environment.
fake_heater_switch3: fake_heater_switch3:
name: Heater 3 name: Heater 3
icon: mdi:radiator icon: mdi:radiator
fake_heater_switch2: fake_heater_switch2:
name: Heater 2 name: Heater 2
icon: mdi:radiator icon: mdi:radiator
fake_heater_switch1: fake_heater_switch1:
name: Heater 1 name: Heater 1
icon: mdi:radiator icon: mdi:radiator
fake_heater_4switch1: fake_heater_4switch1:
name: Heater (multiswitch1) name: Heater (multiswitch1)
icon: mdi:radiator icon: mdi:radiator
fake_heater_4switch2: fake_heater_4switch2:
name: Heater (multiswitch2) name: Heater (multiswitch2)
icon: mdi:radiator icon: mdi:radiator
fake_heater_4switch3: fake_heater_4switch3:
name: Heater (multiswitch3) name: Heater (multiswitch3)
icon: mdi:radiator icon: mdi:radiator
fake_heater_4switch4: fake_heater_4switch4:
name: Heater (multiswitch4) name: Heater (multiswitch4)
icon: mdi:radiator icon: mdi:radiator
# input_boolean to simulate the motion sensor entity. Only for development environment. # input_boolean to simulate the motion sensor entity. Only for development environment.
fake_motion_sensor1: fake_motion_sensor1:
name: Motion Sensor 1 name: Motion Sensor 1
icon: mdi:run icon: mdi:run
# input_boolean to simulate the presence sensor entity. Only for development environment. # input_boolean to simulate the presence sensor entity. Only for development environment.
fake_presence_sensor1: fake_presence_sensor1:
name: Presence Sensor 1 name: Presence Sensor 1
icon: mdi:home icon: mdi:home
climate: climate:
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat1 name: Underlying thermostat1
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
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat2 name: Underlying thermostat2
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
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat3 name: Underlying thermostat3
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
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat4 name: Underlying thermostat4
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
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat5 name: Underlying thermostat5
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
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat6 name: Underlying thermostat6
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
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat7 name: Underlying thermostat7
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
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat8 name: Underlying thermostat8
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
- platform: generic_thermostat - platform: generic_thermostat
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: recorder:
include: include:
domains: domains:
- input_boolean - input_boolean
- input_number - input_number
- switch - switch
- climate - climate
- sensor - sensor
template: template:
- binary_sensor: - binary_sensor:
- name: maison_occupee - name: maison_occupee
unique_id: maison_occupee unique_id: maison_occupee
state: "{{is_state('person.jmc', 'home') }}" state: "{{is_state('person.jmc', 'home') }}"
device_class: occupancy device_class: occupancy
- sensor: - sensor:
- name: "Total énergie switch1" - name: "Total énergie switch1"
unique_id: total_energie_switch1 unique_id: total_energie_switch1
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy device_class: energy
state_class: total_increasing state_class: total_increasing
state: > state: >
{% set energy = state_attr('climate.thermostat_switch_1', 'total_energy') %} {% set energy = state_attr('climate.thermostat_switch_1', 'total_energy') | float(default=-1) %}
{% if energy == 'unavailable' or energy is none%}unavailable{% else %} {% if energy < 0 %}{{none}}{% else %}
{{ ((energy | float) / 1.0) | round(2, default=0) }} {{ energy | round(2, default=0) }}
{% endif %} {% endif %}
- name: "Total énergie climate 2" - name: "Total énergie climate 2"
unique_id: total_energie_climate2 unique_id: total_energie_climate2
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy device_class: energy
state_class: total_increasing state_class: total_increasing
state: > state: >
{% set energy = state_attr('climate.thermostat_climate_2', 'total_energy') %} {% set energy = state_attr('climate.thermostat_climate_2', 'total_energy') | float(default=-1) %}
{% if energy == 'unavailable' or energy is none%}unavailable{% else %} {% if energy < 0 %}{{none}}{% else %}
{{ ((energy | float) / 1.0) | round(2, default=0) }} {{ energy | round(2, default=0) }}
{% endif %} {% endif %}
- name: "Total énergie chambre" - name: "Total énergie chambre"
unique_id: total_energie_chambre unique_id: total_energie_chambre
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy device_class: energy
state_class: total_increasing state_class: total_increasing
state: > state: >
{% set energy = state_attr('climate.thermostat_chambre', 'total_energy') %} {% set energy = state_attr('climate.thermostat_chambre', 'total_energy') | float(default=-1) %}
{% if energy == 'unavailable' or energy is none%}unavailable{% else %} {% if energy < 0 %}{{none}}{% else %}
{{ ((energy | float) / 1.0) | round(2, default=0) }} {{ energy | round(2, default=0) }}
{% endif %} {% endif %}
switch: switch:
- platform: template - platform: template
switches: switches:
pilote_sdb_rdc: pilote_sdb_rdc:
friendly_name: "Pilote chauffage SDB RDC" friendly_name: "Pilote chauffage SDB RDC"
value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}" value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}"
turn_on: turn_on:
service: select.select_option service: select.select_option
data: data:
option: comfort option: comfort
target: target:
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
turn_off: turn_off:
service: select.select_option service: select.select_option
data: data:
option: comfort-2 option: comfort-2
target: target:
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
frontend: frontend:
themes: themes:
versatile_thermostat_theme: versatile_thermostat_theme:
state-binary_sensor-safety-on-color: "#FF0B0B" state-binary_sensor-safety-on-color: "#FF0B0B"
state-binary_sensor-power-on-color: "#FF0B0B" state-binary_sensor-power-on-color: "#FF0B0B"
state-binary_sensor-window-on-color: "rgb(156, 39, 176)" state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)" state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen" state-binary_sensor-presence-on-color: "lightgreen"
@@ -18,7 +18,7 @@ from homeassistant.core import (
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 import DeviceInfo, DeviceEntryType from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
@@ -505,7 +505,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if len(presets): if len(presets):
self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE
for key, val in CONF_PRESETS.items(): # TODO before presets.items(): for key, val in CONF_PRESETS.items():
if val != 0.0: if val != 0.0:
self._attr_preset_modes.append(key) self._attr_preset_modes.append(key)
@@ -1582,6 +1582,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if not new_state: if not new_state:
return return
new_hvac_mode = new_state.state
old_state = event.data.get("old_state") old_state = event.data.get("old_state")
old_hvac_action = ( old_hvac_action = (
old_state.attributes.get("hvac_action") old_state.attributes.get("hvac_action")
@@ -1594,16 +1596,21 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
else None else None
) )
# Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command
if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE:
_LOGGER.debug("The underlying switch to idle instead of OFF. We will consider it as OFF")
new_hvac_mode = HVACMode.OFF
_LOGGER.info( _LOGGER.info(
"%s - Underlying climate changed. Event.new_state is %s, current_hvac_mode=%s, new_hvac_action=%s, old_hvac_action=%s", "%s - Underlying climate changed. Event.new_hvac_mode is %s, current_hvac_mode=%s, new_hvac_action=%s, old_hvac_action=%s",
self, self,
new_state, new_hvac_mode,
self._hvac_mode, self._hvac_mode,
new_hvac_action, new_hvac_action,
old_hvac_action, old_hvac_action,
) )
if new_state.state in [ if new_hvac_mode in [
HVACMode.OFF, HVACMode.OFF,
HVACMode.HEAT, HVACMode.HEAT,
HVACMode.COOL, HVACMode.COOL,
@@ -1611,8 +1618,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
HVACMode.DRY, HVACMode.DRY,
HVACMode.AUTO, HVACMode.AUTO,
HVACMode.FAN_ONLY, HVACMode.FAN_ONLY,
None
]: ]:
self._hvac_mode = new_state.state self._hvac_mode = new_hvac_mode
# Interpretation of hvac # Interpretation of hvac
HVAC_ACTION_ON = [ # pylint: disable=invalid-name HVAC_ACTION_ON = [ # pylint: disable=invalid-name
@@ -2098,9 +2106,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
switch_cond, switch_cond,
) )
ret = False # Issue 99 - a climate is regulated by the device itself and not by VTherm. So a VTherm should never be in security !
if mode_cond and temp_cond and climate_cond: shouldClimateBeInSecurity = False # temp_cond and climate_cond
if not self._security_state: shouldSwitchBeInSecurity = temp_cond and switch_cond
shouldBeInSecurity = shouldClimateBeInSecurity or shouldSwitchBeInSecurity
shouldStartSecurity = mode_cond and not self._security_state and shouldBeInSecurity
# attr_preset_mode is not necessary normaly. It is just here to be sure
shouldStopSecurity = self._security_state and not shouldBeInSecurity and self._attr_preset_mode == PRESET_SECURITY
# Logging and event
if shouldStartSecurity:
if shouldClimateBeInSecurity:
_LOGGER.warning( _LOGGER.warning(
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and underlying climate is %s. Set it into security mode", "%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,
@@ -2109,10 +2126,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
delta_ext_temp, delta_ext_temp,
self.hvac_action, self.hvac_action,
) )
ret = True elif shouldSwitchBeInSecurity:
if mode_cond and temp_cond and switch_cond:
if not self._security_state:
_LOGGER.warning( _LOGGER.warning(
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent (%.2f) is over defined value (%.2f). Set it into security mode", "%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent (%.2f) is over defined value (%.2f). Set it into security mode",
self, self,
@@ -2122,9 +2136,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._prop_algorithm.on_percent, self._prop_algorithm.on_percent,
self._security_min_on_percent, self._security_min_on_percent,
) )
ret = True
if mode_cond and temp_cond and not self._security_state:
self.send_event( self.send_event(
EventType.TEMPERATURE_EVENT, EventType.TEMPERATURE_EVENT,
{ {
@@ -2140,8 +2152,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
}, },
) )
if not self._security_state and ret: if shouldStartSecurity:
self._security_state = ret self._security_state = True
self.save_hvac_mode() self.save_hvac_mode()
self.save_preset_mode() self.save_preset_mode()
await self._async_set_preset_mode_internal(PRESET_SECURITY) await self._async_set_preset_mode_internal(PRESET_SECURITY)
@@ -2167,18 +2179,14 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
}, },
) )
if ( if shouldStopSecurity:
self._security_state
and self._attr_preset_mode == PRESET_SECURITY
and not ret
):
_LOGGER.warning( _LOGGER.warning(
"%s - End of security mode. restoring hvac_mode to %s and preset_mode to %s", "%s - End of security mode. restoring hvac_mode to %s and preset_mode to %s",
self, self,
self._saved_hvac_mode, self._saved_hvac_mode,
self._saved_preset_mode, self._saved_preset_mode,
) )
self._security_state = ret self._security_state = False
# Restore hvac_mode if previously saved # Restore hvac_mode if previously saved
if self._is_over_climate or self._security_default_on_percent <= 0.0: if self._is_over_climate or self._security_default_on_percent <= 0.0:
await self.restore_hvac_mode(False) await self.restore_hvac_mode(False)
@@ -2201,7 +2209,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
}, },
) )
return ret return shouldBeInSecurity
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"""
@@ -4,7 +4,8 @@ from datetime import timedelta
from homeassistant.core import HomeAssistant, callback, Event from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity, DeviceInfo, DeviceEntryType from homeassistant.helpers.entity import Entity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event, async_call_later from homeassistant.helpers.event import async_track_state_change_event, async_call_later
from .climate import VersatileThermostat from .climate import VersatileThermostat
@@ -1,2 +1,2 @@
homeassistant homeassistant==2023.9.0
ffmpeg ffmpeg
@@ -1,4 +1,4 @@
# -r requirements_dev.txt -r requirements_dev.txt
# aiodiscover # aiodiscover
ulid_transform ulid_transform
pytest-homeassistant-custom-component pytest-homeassistant-custom-component
@@ -1,120 +1,121 @@
reload: reload:
description: Reload all Versatile Thermostat entities. name: Reload
description: Reload all Versatile Thermostat entities.
set_presence: set_presence:
name: Set presence name: Set presence
description: Force the presence mode in thermostat description: Force the presence mode in thermostat
target: target:
entity: entity:
integration: versatile_thermostat integration: versatile_thermostat
fields: fields:
presence: presence:
name: Presence name: Presence
description: Presence setting description: Presence setting
required: true required: true
advanced: false advanced: false
example: "on" example: "on"
default: "on" default: "on"
selector: selector:
select: select:
options: options:
- "on" - "on"
- "off" - "off"
- "home" - "home"
- "not_home" - "not_home"
set_preset_temperature: set_preset_temperature:
name: Set temperature preset name: Set temperature preset
description: Change the target temperature of a preset description: Change the target temperature of a preset
target: target:
entity: entity:
integration: versatile_thermostat integration: versatile_thermostat
fields: fields:
preset: preset:
name: Preset name: Preset
description: Preset name description: Preset name
required: true required: true
advanced: false advanced: false
example: "comfort" example: "comfort"
selector: selector:
select: select:
options: options:
- "eco" - "eco"
- "comfort" - "comfort"
- "boost" - "boost"
temperature: temperature:
name: Temperature when present name: Temperature when present
description: Target temperature for the preset when present description: Target temperature for the preset when present
required: false required: false
advanced: false advanced: false
example: "19.5" example: "19.5"
default: "17" default: "17"
selector: selector:
number: number:
min: 7 min: 7
max: 35 max: 35
step: 0.1 step: 0.1
unit_of_measurement: ° unit_of_measurement: °
mode: slider mode: slider
temperature_away: temperature_away:
name: Temperature when not present name: Temperature when not present
description: Target temperature for the preset when not present description: Target temperature for the preset when not present
required: false required: false
advanced: false advanced: false
example: "17" example: "17"
default: "15" default: "15"
selector: selector:
number: number:
min: 7 min: 7
max: 35 max: 35
step: 0.1 step: 0.1
unit_of_measurement: ° unit_of_measurement: °
mode: slider mode: slider
set_security: set_security:
name: Set security name: Set security
description: Change the security parameters description: Change the security parameters
target: target:
entity: entity:
integration: versatile_thermostat integration: versatile_thermostat
fields: fields:
delay_min: delay_min:
name: Delay in minutes name: Delay in minutes
description: Maximum allowed delay in minutes between two temperature mesures description: Maximum allowed delay in minutes between two temperature mesures
required: false required: false
advanced: false advanced: false
example: "30" example: "30"
selector: selector:
number: number:
min: 0 min: 0
max: 9999 max: 9999
unit_of_measurement: "min" unit_of_measurement: "min"
mode: box mode: box
min_on_percent: min_on_percent:
name: Minimal on_percent name: Minimal on_percent
description: Minimal heating percent value for security preset activation description: Minimal heating percent value for security preset activation
required: false required: false
advanced: false advanced: false
example: "0.5" example: "0.5"
default: "0.5" default: "0.5"
selector: selector:
number: number:
min: 0 min: 0
max: 1 max: 1
step: 0.05 step: 0.05
unit_of_measurement: "%" unit_of_measurement: "%"
mode: slider mode: slider
default_on_percent: default_on_percent:
name: on_percent used in security mode name: on_percent used in security mode
description: The default heating percent value in security preset description: The default heating percent value in security preset
required: false required: false
advanced: false advanced: false
example: "0.1" example: "0.1"
default: "0.1" default: "0.1"
selector: selector:
number: number:
min: 0 min: 0
max: 1 max: 1
step: 0.05 step: 0.05
unit_of_measurement: "%" unit_of_measurement: "%"
mode: slider mode: slider
@@ -15,6 +15,7 @@ from custom_components.versatile_thermostat.const import (
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_AC_MODE,
CONF_TEMP_SENSOR, CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR, CONF_EXTERNAL_TEMP_SENSOR,
CONF_CYCLE_MIN, CONF_CYCLE_MIN,
@@ -112,6 +113,7 @@ MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = { MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
CONF_CLIMATE: "climate.mock_climate", CONF_CLIMATE: "climate.mock_climate",
CONF_AC_MODE: False,
} }
MOCK_PRESETS_CONFIG = { MOCK_PRESETS_CONFIG = {
@@ -94,7 +94,7 @@ async def test_window_management_time_not_enough(
await try_window_condition(None) await try_window_condition(None)
assert entity.window_state == STATE_OFF assert entity.window_state == STATE_OFF
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -234,7 +234,7 @@ async def test_window_management_time_enough(
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -418,7 +418,7 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -561,7 +561,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -670,4 +670,4 @@ async def test_window_auto_no_on_percent(
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity # Clean the entity
await entity.remove_thermostat() entity.remove_thermostat()
@@ -275,7 +275,7 @@ class UnderlyingSwitch(UnderlyingEntity):
self._on_time_sec, self._on_time_sec,
) )
await self._cancel_cycle() self._cancel_cycle()
if self._hvac_mode == HVACMode.OFF: if self._hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self) _LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
@@ -327,7 +327,7 @@ class UnderlyingSwitch(UnderlyingEntity):
self._should_relaunch_control_heating, self._should_relaunch_control_heating,
self._off_time_sec, self._off_time_sec,
) )
await self._cancel_cycle() self._cancel_cycle()
if self._hvac_mode == HVACMode.OFF: if self._hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self) _LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
@@ -446,7 +446,7 @@ class UnderlyingClimate(UnderlyingEntity):
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 self.is_initialized: if self.is_initialized:
return self._underlying_climate.hvac_action not in [ return self._underlying_climate.hvac_mode != HVACMode.OFF and self._underlying_climate.hvac_action not in [
HVACAction.IDLE, HVACAction.IDLE,
HVACAction.OFF, HVACAction.OFF,
] ]