Feature 124 add inversion pilot wire (#149)

* Add inverse switch command for ThermostatOverSwitch

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
Jean-Marc Collin
2023-11-01 12:08:13 +01:00
committed by GitHub
parent dd7d6c97b3
commit 2ebeac30e6
16 changed files with 292 additions and 68 deletions

View File

@@ -104,6 +104,7 @@ from .const import (
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_INVERSE_SWITCH,
UnknownEntity,
WindowOpenDetectionMethod,
)
@@ -241,6 +242,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
]
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(CONF_INVERSE_SWITCH, default=False): cv.boolean,
}
)

View File

@@ -90,6 +90,7 @@ CONF_AUTO_REGULATION_MEDIUM= "auto_regulation_medium"
CONF_AUTO_REGULATION_STRONG= "auto_regulation_strong"
CONF_AUTO_REGULATION_DTEMP="auto_regulation_dtemp"
CONF_AUTO_REGULATION_PERIOD_MIN="auto_regulation_periode_min"
CONF_INVERSE_SWITCH="inverse_switch_command"
CONF_PRESETS = {
p: f"{p}_temp"
@@ -193,7 +194,8 @@ ALL_CONF = (
CONF_VALVE_4,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_INVERSE_SWITCH
]
+ CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES

View File

@@ -26,6 +26,10 @@ class PITemperatureRegulator:
self.accumulated_error:float = 0
self.accumulated_error_threshold:float = accumulated_error_threshold
def reset_accumulated_error(self):
""" Reset the accumulated error """
self.accumulated_error = 0
def set_target_temp(self, target_temp):
""" Set the new target_temp"""
self.target_temp = target_temp

View File

@@ -41,7 +41,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -60,7 +61,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -208,7 +210,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -227,7 +230,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {

View File

@@ -11,6 +11,7 @@ from .const import (
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_INVERSE_SWITCH,
overrides
)
@@ -34,12 +35,18 @@ class ThermostatOverSwitch(BaseThermostat):
# def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
# """Initialize the thermostat over switch."""
# super().__init__(hass, unique_id, name, entry_infos)
_is_inversed: bool = None
@property
def is_over_switch(self) -> bool:
""" True if the Thermostat is over_switch"""
return True
@property
def is_inversed(self) -> bool:
""" True if the switch is inversed (for pilot wire and diode)"""
return self._is_inversed is True
@overrides
def post_init(self, entry_infos):
""" Initialize the Thermostat"""
@@ -73,6 +80,7 @@ class ThermostatOverSwitch(BaseThermostat):
)
)
self._is_inversed = entry_infos.get(CONF_INVERSE_SWITCH) is True
self._should_relaunch_control_heating = False
@overrides

View File

@@ -41,7 +41,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -60,7 +61,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -208,7 +210,8 @@
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -227,7 +230,8 @@
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {

View File

@@ -41,7 +41,8 @@
"valve_entity4_id": "4ème valve number",
"auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
"auto_regulation_periode_min": "Période minimale de régulation",
"inverse_switch_command": "Inverser la commande"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -60,7 +61,8 @@
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la température cible",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode"
}
},
"tpi": {
@@ -209,7 +211,8 @@
"valve_entity4_id": "4ème valve",
"auto_regulation_mode": "Auto-regulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
"auto_regulation_periode_min": "Période minimale de régulation",
"inverse_switch_command": "Inverser la commande"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -228,7 +231,8 @@
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode"
}
},
"tpi": {

View File

@@ -39,7 +39,8 @@
"valve_entity2_id": "Secondo valvola numero",
"valve_entity3_id": "Terzo valvola numero",
"valve_entity4_id": "Quarto valvola numero",
"auto_regulation_mode": "Autoregolamentazione"
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -56,7 +57,8 @@
"valve_entity2_id": "Entity id del secondo valvola numero",
"valve_entity3_id": "Entity id del terzo valvola numero",
"valve_entity4_id": "Entity id del quarto valvola numero",
"auto_regulation_mode": "Regolazione automatica della temperatura target"
"auto_regulation_mode": "Regolazione automatica della temperatura target",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
}
},
"tpi": {
@@ -195,7 +197,8 @@
"valve_entity2_id": "Secondo valvola numero",
"valve_entity3_id": "Terzo valvola numero",
"valve_entity4_id": "Quarto valvola numero",
"auto_regulation_mode": "Autoregolamentazione"
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -212,7 +215,8 @@
"valve_entity2_id": "Entity id del secondo valvola numero",
"valve_entity3_id": "Entity id del terzo valvola numero",
"valve_entity4_id": "Entity id del quarto valvola numero",
"auto_regulation_mode": "Autoregolamentazione"
"auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
}
},
"tpi": {

View File

@@ -38,7 +38,11 @@
"valve_entity_id": "1. ventil číslo",
"valve_entity2_id": "2. ventil číslo",
"valve_entity3_id": "3. ventil číslo",
"valve_entity4_id": "4. ventil číslo"
"valve_entity4_id": "4. ventil číslo",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "ID entity povinného ohrievača",
@@ -54,7 +58,11 @@
"valve_entity_id": "1. ventil číslo entity id",
"valve_entity2_id": "2. ventil číslo entity id",
"valve_entity3_id": "3. ventil číslo entity id",
"valve_entity4_id": "4. ventil číslo entity id"
"valve_entity4_id": "4. ventil číslo entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -199,7 +207,11 @@
"valve_entity_id": "1. ventil číslo",
"valve_entity2_id": "2. ventil číslo",
"valve_entity3_id": "3. ventil číslo",
"valve_entity4_id": "4. ventil číslo"
"valve_entity4_id": "4. ventil číslo",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period",
"inverse_switch_command": "Inverse switch command"
},
"data_description": {
"heater_entity_id": "ID entity povinného ohrievača",
@@ -215,7 +227,11 @@
"valve_entity_id": "1. ventil číslo entity id",
"valve_entity2_id": "2. ventil číslo entity id",
"valve_entity3_id": "3. ventil číslo entity id",
"valve_entity4_id": "4. ventil číslo entity id"
"valve_entity4_id": "4. ventil číslo entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command"
}
},
"tpi": {
@@ -329,6 +345,14 @@
"thermostat_over_climate": "Termostat nad iným termostatom",
"thermostat_over_valve": "Thermostat over a valve"
}
},
"auto_regulation_mode": {
"options": {
"auto_regulation_strong": "Strong",
"auto_regulation_medium": "Medium",
"auto_regulation_light": "Light",
"auto_regulation_none": "No auto-regulation"
}
}
},
"entity": {

View File

@@ -9,7 +9,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, UnitOfTemperature
from homeassistant.exceptions import ServiceNotFound
from homeassistant.core import HomeAssistant, DOMAIN as HA_DOMAIN, CALLBACK_TYPE
from homeassistant.core import HomeAssistant, CALLBACK_TYPE
from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
@@ -104,37 +104,27 @@ class UnderlyingEntity:
"""If the toggleable device is currently active."""
return None
async def turn_off(self):
"""Turn heater toggleable device off."""
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
# This may fails if called after shutdown
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
HA_DOMAIN,
SERVICE_TURN_OFF,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
async def turn_on(self):
"""Turn heater toggleable device on."""
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
HA_DOMAIN,
SERVICE_TURN_ON,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
async def set_temperature(self, temperature, max_temp, min_temp):
"""Set the target temperature"""
return
# This should be the correct way to handle turn_off and turn_on but this breaks the unit test
# will an not understandable error: TypeError: object MagicMock can't be used in 'await' expression
async def turn_off(self):
""" Turn off the underlying equipement.
Need to be overriden"""
return NotImplementedError
async def turn_on(self):
""" Turn off the underlying equipement.
Need to be overriden"""
return NotImplementedError
@property
def is_inversed(self):
""" Tells if the switch command should be inversed"""
return False
def remove_entity(self):
"""Remove the underlying entity"""
return
@@ -212,6 +202,13 @@ class UnderlyingSwitch(UnderlyingEntity):
"""The initial delay for this class"""
return self._initial_delay_sec
@overrides
@property
def is_inversed(self):
""" Tells if the switch command should be inversed"""
return self._thermostat.is_inversed
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode. Returns true if something have change"""
@@ -229,7 +226,41 @@ class UnderlyingSwitch(UnderlyingEntity):
@property
def is_device_active(self):
"""If the toggleable device is currently active."""
return self._hass.states.is_state(self._entity_id, STATE_ON)
real_state = self._hass.states.is_state(self._entity_id, STATE_ON)
return self.is_inversed and not real_state
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
async def turn_off(self):
"""Turn heater toggleable device off."""
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON
domain = self._entity_id.split('.')[0]
# This may fails if called after shutdown
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
domain,
command,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
async def turn_on(self):
"""Turn heater toggleable device on."""
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF
domain = self._entity_id.split('.')[0]
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(
domain,
command,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
@overrides
async def start_cycle(
@@ -380,6 +411,7 @@ class UnderlyingSwitch(UnderlyingEntity):
# increment energy at the end of the cycle
self._thermostat.incremente_energy()
@overrides
def remove_entity(self):
"""Remove the entity after stopping its cycle"""
self._cancel_cycle()