From 11ae58bde42de205f891e88a5118c38736d3b983 Mon Sep 17 00:00:00 2001 From: ScratMan Date: Thu, 6 Jul 2023 06:55:28 +0200 Subject: [PATCH 1/3] Add force_off_state parameter to select if thermostat should force heater OFF when disabled. --- README.md | 3 +++ custom_components/smart_thermostat/climate.py | 6 +++++- custom_components/smart_thermostat/const.py | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1957f36..6dfb1ac 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,9 @@ tolerance is 0.5 the heater will stop when the sensor equals or goes above 25.5 0.3). * **ac_mode** (Optional): Set the switch specified in the heater option to be treated as a cooling device instead of a heating device. Should be a boolean (default: false). +* **force_off_state** (Optional): If set to true (default value), Home Assistant will force the +heater entity to OFF state when the thermostat is in OFF. Set parameter to false to be able to +control the heater entity externally while the thermostat is OFF. * **preset_sync_mode** (Optional): If set to sync mode, manually setting a temperature will enable the corresponding preset. In example, if away temperature is set to 14°C, manually setting the temperature to 14°C on the thermostat will automatically enable the away preset mode. Should be diff --git a/custom_components/smart_thermostat/climate.py b/custom_components/smart_thermostat/climate.py index 9d5a2fc..80a52cc 100644 --- a/custom_components/smart_thermostat/climate.py +++ b/custom_components/smart_thermostat/climate.py @@ -71,6 +71,7 @@ vol.Required(const.CONF_SENSOR): cv.entity_id, vol.Optional(const.CONF_OUTDOOR_SENSOR): cv.entity_id, vol.Optional(const.CONF_AC_MODE): cv.boolean, + vol.Optional(const.CONF_FORCE_OFF_STATE, default=True): cv.boolean, vol.Optional(const.CONF_MAX_TEMP): vol.Coerce(float), vol.Optional(const.CONF_MIN_TEMP): vol.Coerce(float), vol.Optional(CONF_NAME, default=const.DEFAULT_NAME): cv.string, @@ -151,6 +152,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= 'hot_tolerance': config.get(const.CONF_HOT_TOLERANCE), 'cold_tolerance': config.get(const.CONF_COLD_TOLERANCE), 'ac_mode': config.get(const.CONF_AC_MODE), + 'force_off_state': config.get(const.CONF_FORCE_OFF_STATE), 'min_cycle_duration': config.get(const.CONF_MIN_CYCLE_DURATION), 'min_off_cycle_duration': config.get(const.CONF_MIN_OFF_CYCLE_DURATION), 'min_cycle_duration_pid_off': config.get(const.CONF_MIN_CYCLE_DURATION_PID_OFF), @@ -245,6 +247,7 @@ def __init__(self, **kwargs): if self._unique_id == 'none': self._unique_id = slugify(f"{DOMAIN}_{self._name}_{self._heater_entity_id}") self._ac_mode = kwargs.get('ac_mode') + self._force_off_state = kwargs.get('force_off_state', True) self._keep_alive = kwargs.get('keep_alive') self._sampling_period = kwargs.get('sampling_period').seconds self._sensor_stall = kwargs.get('sensor_stall').seconds @@ -867,7 +870,8 @@ async def _async_control_heating(self, time_func=None, calc_pid=False): "Thermostat.", self.entity_id, self._current_temp, self._target_temp) if not self._active or self._hvac_mode == HVACMode.OFF: - if self._hvac_mode == HVACMode.OFF and self._is_device_active: + if self._force_off_state and self._hvac_mode == HVACMode.OFF and \ + self._is_device_active: _LOGGER.debug("%s: %s is active while HVAC mode is %s. Turning it OFF.", self.entity_id, self._heater_entity_id, self._hvac_mode) if self._pwm: diff --git a/custom_components/smart_thermostat/const.py b/custom_components/smart_thermostat/const.py index 1d5aeea..4693fa2 100644 --- a/custom_components/smart_thermostat/const.py +++ b/custom_components/smart_thermostat/const.py @@ -28,6 +28,7 @@ CONF_HOT_TOLERANCE = "hot_tolerance" CONF_COLD_TOLERANCE = "cold_tolerance" CONF_AC_MODE = "ac_mode" +CONF_FORCE_OFF_STATE = "force_off_state" CONF_MIN_CYCLE_DURATION = "min_cycle_duration" CONF_MIN_OFF_CYCLE_DURATION = "min_off_cycle_duration" CONF_MIN_CYCLE_DURATION_PID_OFF = 'min_cycle_duration_pid_off' From 6719299d56eaefcac8efb21e4b964b2802253e13 Mon Sep 17 00:00:00 2001 From: ScratMan Date: Wed, 12 Jul 2023 22:38:28 +0200 Subject: [PATCH 2/3] Parameter 'ac_mode' enables heating and cooling modes, with optional dedicated entity for cooling. --- README.md | 13 +- custom_components/smart_thermostat/climate.py | 131 +++++++++++++----- custom_components/smart_thermostat/const.py | 1 + .../pid_controller/__init__.py | 20 ++- 4 files changed, 127 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 6dfb1ac..7ba48b3 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,9 @@ gains to quickly test the behavior without waiting the integral to stabilize by * **heater** (Required): entity_id for heater control, should be a toggle device or a valve accepting direct input between 0% and 100%. If a valve is used, pwm parameter should be set to 0. Becomes air conditioning switch when ac_mode is set to true. +* **cooler** (Optional): entity_id for cooling control, should be a toggle device or a valve +accepting direct input between 0% and 100%. If a valve is used, pwm parameter should be set to 0. +Becomes air conditioning switch when ac_mode is set to true. * **invert_heater** (Optional): if set to true, inverts the polarity of heater switch (switch is on while idle and off while active). Must be a boolean (defaults to false). * **target_sensor** (Required): entity_id for a temperature sensor, target_sensor.state must be @@ -251,15 +254,15 @@ temperature read by the sensor specified in the target_sensor option and the tar that must change prior to being switched off. For example, if the target temperature is 25 and the tolerance is 0.5 the heater will stop when the sensor equals or goes above 25.5 (float, default 0.3). -* **ac_mode** (Optional): Set the switch specified in the heater option to be treated as a cooling -device instead of a heating device. Should be a boolean (default: false). +* **ac_mode** (Optional): Set the switch specified in the heater option to be treated as a +heating/cooling device instead of a pure heating device. Should be a boolean (default: false). * **force_off_state** (Optional): If set to true (default value), Home Assistant will force the -heater entity to OFF state when the thermostat is in OFF. Set parameter to false to be able to -control the heater entity externally while the thermostat is OFF. +heater entity to OFF state when the thermostat is in OFF. Set parameter to false to control the +heater entity externally while the thermostat is OFF. * **preset_sync_mode** (Optional): If set to sync mode, manually setting a temperature will enable the corresponding preset. In example, if away temperature is set to 14°C, manually setting the temperature to 14°C on the thermostat will automatically enable the away preset mode. Should be -string either 'sync' or 'none' (default: 'none'). +a string, either 'sync' or 'none' (default: 'none'). * **boost_pid_off** (Optional): When set to true, the PID will be set to OFF state while boost preset is selected, and the thermostat will operate in hysteresis mode. This helps to quickly raise the temperature in a room for a short period of time. Should be a boolean (default: false). diff --git a/custom_components/smart_thermostat/climate.py b/custom_components/smart_thermostat/climate.py index 80a52cc..10fedce 100644 --- a/custom_components/smart_thermostat/climate.py +++ b/custom_components/smart_thermostat/climate.py @@ -67,6 +67,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(const.CONF_HEATER): cv.entity_id, + vol.Optional(const.CONF_COOLER): cv.entity_id, vol.Required(const.CONF_INVERT_HEATER, default=False): cv.boolean, vol.Required(const.CONF_SENSOR): cv.entity_id, vol.Optional(const.CONF_OUTDOOR_SENSOR): cv.entity_id, @@ -143,6 +144,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= 'name': config.get(CONF_NAME), 'unique_id': config.get(CONF_UNIQUE_ID), 'heater_entity_id': config.get(const.CONF_HEATER), + 'cooler_entity_id': config.get(const.CONF_COOLER), 'invert_heater': config.get(const.CONF_INVERT_HEATER), 'sensor_entity_id': config.get(const.CONF_SENSOR), 'ext_sensor_entity_id': config.get(const.CONF_OUTDOOR_SENSOR), @@ -241,12 +243,13 @@ def __init__(self, **kwargs): self._name = kwargs.get('name') self._unique_id = kwargs.get('unique_id') self._heater_entity_id = kwargs.get('heater_entity_id') + self._cooler_entity_id = kwargs.get('cooler_entity_id', None) self._heater_polarity_invert = kwargs.get('invert_heater') self._sensor_entity_id = kwargs.get('sensor_entity_id') self._ext_sensor_entity_id = kwargs.get('ext_sensor_entity_id') if self._unique_id == 'none': self._unique_id = slugify(f"{DOMAIN}_{self._name}_{self._heater_entity_id}") - self._ac_mode = kwargs.get('ac_mode') + self._ac_mode = kwargs.get('ac_mode', False) self._force_off_state = kwargs.get('force_off_state', True) self._keep_alive = kwargs.get('keep_alive') self._sampling_period = kwargs.get('sampling_period').seconds @@ -301,7 +304,7 @@ def __init__(self, **kwargs): ClimateEntityFeature.PRESET_MODE self._difference = kwargs.get('difference') if self._ac_mode: - self._attr_hvac_modes = [HVACMode.COOL, HVACMode.OFF] + self._attr_hvac_modes = [HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF] self._min_out = -self._difference self._max_out = 0 else: @@ -375,6 +378,12 @@ async def async_added_to_hass(self): self.hass, self._heater_entity_id, self._async_switch_changed)) + if self._cooler_entity_id is not None: + self.async_on_remove( + async_track_state_change( + self.hass, + self._cooler_entity_id, + self._async_switch_changed)) if self._keep_alive: self.async_on_remove( async_track_time_interval( @@ -423,7 +432,7 @@ def _async_startup(*_): self._i = float(old_state.attributes.get('pid_i')) self._pid_controller.integral = self._i if not self._hvac_mode and old_state.state: - self._hvac_mode = old_state.state + self.set_hvac_mode(old_state.state) if old_state.attributes.get('kp') is not None and self._pid_controller is not None: self._kp = float(old_state.attributes.get('kp')) self._pid_controller.set_pid_param(kp=self._kp) @@ -518,7 +527,7 @@ def hvac_action(self): return HVACAction.OFF if not self._is_device_active: return HVACAction.IDLE - if self._ac_mode: + elif self._hvac_mode == HVACMode.COOL: return HVACAction.COOLING return HVACAction.HEATING @@ -664,17 +673,46 @@ def extra_state_attributes(self): }) return device_state_attributes + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target hvac mode.""" + if hvac_mode == HVACMode.HEAT: + self._min_out = 0 + self._max_out = self._difference + self._hvac_mode = HVACMode.HEAT + elif hvac_mode == HVACMode.COOL: + self._min_out = -self._difference + self._max_out = 0 + self._hvac_mode = HVACMode.COOL + elif hvac_mode == HVACMode.HEAT_COOL: + self._min_out = -self._difference + self._max_out = self._difference + self._hvac_mode = HVACMode.HEAT_COOL + elif hvac_mode == HVACMode.OFF: + self._hvac_mode = HVACMode.OFF + self._control_output = 0 + self._previous_temp = None + self._previous_temp_time = None + if self._pid_controller is not None: + self._pid_controller.clear_samples() + if self._pid_controller: + self._pid_controller.out_max = self._max_out + self._pid_controller.out_min = self._min_out + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" + await self._async_heater_turn_off(force=True) if hvac_mode == HVACMode.HEAT: + self._min_out = 0 + self._max_out = self._difference self._hvac_mode = HVACMode.HEAT - await self._async_control_heating(calc_pid=True) elif hvac_mode == HVACMode.COOL: + self._min_out = -self._difference + self._max_out = 0 self._hvac_mode = HVACMode.COOL - await self._async_control_heating(calc_pid=True) elif hvac_mode == HVACMode.HEAT_COOL: + self._min_out = -self._difference + self._max_out = self._difference self._hvac_mode = HVACMode.HEAT_COOL - await self._async_control_heating(calc_pid=True) elif hvac_mode == HVACMode.OFF: self._hvac_mode = HVACMode.OFF self._control_output = 0 @@ -688,6 +726,12 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: _LOGGER.debug("%s: Set heater to %s from async_set_hvac_mode(%s)", self.entity_id, self._control_output, hvac_mode) await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + if self._cooler_entity_id is not None: + data = {ATTR_ENTITY_ID: self._cooler_entity_id, + ATTR_VALUE: self._control_output} + _LOGGER.debug("%s: Set cooler to %s from async_set_hvac_mode(%s)", self.entity_id, + self._control_output, hvac_mode) + await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) # Clear the samples to avoid integrating the off period self._previous_temp = None self._previous_temp_time = None @@ -696,6 +740,11 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: else: _LOGGER.error("%s: Unrecognized HVAC mode: %s", self.entity_id, hvac_mode) return + if self._pid_controller: + self._pid_controller.out_max = self._max_out + self._pid_controller.out_min = self._min_out + if self._hvac_mode != HVACMode.OFF: + await self._async_control_heating(calc_pid=True) # Ensure we update the current operation after changing the mode self.async_write_ha_state() @@ -873,7 +922,7 @@ async def _async_control_heating(self, time_func=None, calc_pid=False): if self._force_off_state and self._hvac_mode == HVACMode.OFF and \ self._is_device_active: _LOGGER.debug("%s: %s is active while HVAC mode is %s. Turning it OFF.", - self.entity_id, self._heater_entity_id, self._hvac_mode) + self.entity_id, self.heater_or_cooler_entity, self._hvac_mode) if self._pwm: await self._async_heater_turn_off(force=True) else: @@ -881,6 +930,10 @@ async def _async_control_heating(self, time_func=None, calc_pid=False): data = {ATTR_ENTITY_ID: self._heater_entity_id, ATTR_VALUE: self._control_output} await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + if self._cooler_entity_id is not None: + data = {ATTR_ENTITY_ID: self._cooler_entity_id, + ATTR_VALUE: self._control_output} + await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) self.async_write_ha_state() return @@ -897,19 +950,26 @@ async def _async_control_heating(self, time_func=None, calc_pid=False): def _is_device_active(self): """If the toggleable device is currently active.""" if self._heater_polarity_invert: - return self.hass.states.is_state(self._heater_entity_id, STATE_OFF) - return self.hass.states.is_state(self._heater_entity_id, STATE_ON) + return self.hass.states.is_state(self.heater_or_cooler_entity, STATE_OFF) + return self.hass.states.is_state(self.heater_or_cooler_entity, STATE_ON) @property def supported_features(self): """Return the list of supported features.""" return self._support_flags + @property + def heater_or_cooler_entity(self): + """Return the entity to be controlled based on HVAC MODE""" + if self.hvac_mode == HVACMode.COOL and self._cooler_entity_id is not None: + return self._cooler_entity_id + return self._heater_entity_id + async def _async_heater_turn_on(self): """Turn heater toggleable device on.""" if time.time() - self._last_heat_cycle_time >= self._min_off_cycle_duration.seconds: - data = {ATTR_ENTITY_ID: self._heater_entity_id} - _LOGGER.info("%s: Turning ON %s", self.entity_id, self._heater_entity_id) + data = {ATTR_ENTITY_ID: self.heater_or_cooler_entity} + _LOGGER.info("%s: Turning ON %s", self.entity_id, self.heater_or_cooler_entity) if self._heater_polarity_invert: service = SERVICE_TURN_OFF else: @@ -918,22 +978,29 @@ async def _async_heater_turn_on(self): self._last_heat_cycle_time = time.time() else: _LOGGER.info("%s: Reject request turning ON %s: Cycle is too short", - self.entity_id, self._heater_entity_id) + self.entity_id, self.heater_or_cooler_entity) async def _async_heater_turn_off(self, force=False): """Turn heater toggleable device off.""" - if time.time() - self._last_heat_cycle_time >= self._min_on_cycle_duration.seconds or force: - data = {ATTR_ENTITY_ID: self._heater_entity_id} - _LOGGER.info("%s: Turning OFF %s", self.entity_id, self._heater_entity_id) - if self._heater_polarity_invert: - service = SERVICE_TURN_ON + if self._pwm: + if time.time() - self._last_heat_cycle_time >= self._min_on_cycle_duration.seconds or force: + data = {ATTR_ENTITY_ID: self.heater_or_cooler_entity} + _LOGGER.info("%s: Turning OFF %s", self.entity_id, self.heater_or_cooler_entity) + if self._heater_polarity_invert: + service = SERVICE_TURN_ON + else: + service = SERVICE_TURN_OFF + await self.hass.services.async_call(HA_DOMAIN, service, data) + self._last_heat_cycle_time = time.time() else: - service = SERVICE_TURN_OFF - await self.hass.services.async_call(HA_DOMAIN, service, data) - self._last_heat_cycle_time = time.time() + _LOGGER.info("%s: Reject request turning OFF %s: Cycle is too short", + self.entity_id, self.heater_or_cooler_entity) else: - _LOGGER.info("%s: Reject request turning OFF %s: Cycle is too short", - self.entity_id, self._heater_entity_id) + _LOGGER.info("%s: Change state of %s to %s", self.entity_id, + self.heater_or_cooler_entity, 0) + # self.hass.states.async_set(self._heater_entity_id, self._control_output) + data = {ATTR_ENTITY_ID: self.heater_or_cooler_entity, ATTR_VALUE: 0} + await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) async def async_set_preset_mode(self, preset_mode: str): """Set new preset mode. @@ -1038,7 +1105,7 @@ async def set_control_value(self): if abs(self._control_output) == self._difference: if not self._is_device_active: _LOGGER.info("%s: Output is %s. Request turning ON %s", self.entity_id, - self._difference, self._heater_entity_id) + self._difference, self.heater_or_cooler_entity) await self._async_heater_turn_on() self._time_changed = time.time() elif abs(self._control_output) > 0: @@ -1046,14 +1113,16 @@ async def set_control_value(self): else: if self._is_device_active: _LOGGER.info("%s: Output is 0. Request turning OFF %s", self.entity_id, - self._heater_entity_id) + self.heater_or_cooler_entity) await self._async_heater_turn_off() self._time_changed = time.time() else: - _LOGGER.info("%s: Change state of %s to %s", self.entity_id, self._heater_entity_id, + _LOGGER.info("%s: Change state of %s to %s", self.entity_id, + self.heater_or_cooler_entity, round(self._control_output, 2)) # self.hass.states.async_set(self._heater_entity_id, self._control_output) - data = {ATTR_ENTITY_ID: self._heater_entity_id, ATTR_VALUE: self._control_output} + data = {ATTR_ENTITY_ID: self.heater_or_cooler_entity, + ATTR_VALUE: abs(self._control_output)} await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) async def pwm_switch(self, time_on, time_off, time_passed): @@ -1061,20 +1130,20 @@ async def pwm_switch(self, time_on, time_off, time_passed): if self._is_device_active: if time_on <= time_passed or self._force_off: _LOGGER.info("%s: ON time passed. Request turning OFF %s", self.entity_id, - self._heater_entity_id) + self.heater_or_cooler_entity) await self._async_heater_turn_off() self._time_changed = time.time() else: _LOGGER.info("%s: Time until %s turns OFF: %s sec", self.entity_id, - self._heater_entity_id, int(time_on - time_passed)) + self.heater_or_cooler_entity, int(time_on - time_passed)) else: if time_off <= time_passed or self._force_on: _LOGGER.info("%s: OFF time passed. Request turning ON %s", self.entity_id, - self._heater_entity_id) + self.heater_or_cooler_entity) await self._async_heater_turn_on() self._time_changed = time.time() else: _LOGGER.info("%s: Time until %s turns ON: %s sec", self.entity_id, - self._heater_entity_id, int(time_off - time_passed)) + self.heater_or_cooler_entity, int(time_off - time_passed)) self._force_on = False self._force_off = False diff --git a/custom_components/smart_thermostat/const.py b/custom_components/smart_thermostat/const.py index 4693fa2..ba85700 100644 --- a/custom_components/smart_thermostat/const.py +++ b/custom_components/smart_thermostat/const.py @@ -19,6 +19,7 @@ DEFAULT_PRESET_SYNC_MODE = "none" CONF_HEATER = "heater" +CONF_COOLER = "cooler" CONF_INVERT_HEATER = 'invert_heater' CONF_SENSOR = "target_sensor" CONF_OUTDOOR_SENSOR = "outdoor_sensor" diff --git a/custom_components/smart_thermostat/pid_controller/__init__.py b/custom_components/smart_thermostat/pid_controller/__init__.py index 3a2f785..9177828 100644 --- a/custom_components/smart_thermostat/pid_controller/__init__.py +++ b/custom_components/smart_thermostat/pid_controller/__init__.py @@ -11,8 +11,8 @@ class PID: error: float - def __init__(self, kp, ki, kd, ke=0, out_min=float('-inf'), out_max=float('+inf'), sampling_period=0, - cold_tolerance=0.3, hot_tolerance=0.3): + def __init__(self, kp, ki, kd, ke=0, out_min=float('-inf'), out_max=float('+inf'), + sampling_period=0, cold_tolerance=0.3, hot_tolerance=0.3): """A proportional-integral-derivative controller. :param kp: Proportional coefficient. :type kp: float @@ -80,6 +80,22 @@ def mode(self, mode): assert mode.upper() in ['AUTO', 'OFF'] self._mode = mode.upper() + @property + def out_max(self): + return self._out_max + + @out_max.setter + def out_max(self, out_max): + self._out_max = out_max + + @property + def out_min(self): + return self._out_min + + @out_min.setter + def out_min(self, out_min): + self._out_min = out_min + @property def sampling_period(self): return self._sampling_period From f12ba98f493656f1f570310f25f37cab955d0ce6 Mon Sep 17 00:00:00 2001 From: ScratMan Date: Thu, 13 Jul 2023 07:59:58 +0200 Subject: [PATCH 3/3] Allow using input_number entities for heater or cooler. --- custom_components/smart_thermostat/climate.py | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/custom_components/smart_thermostat/climate.py b/custom_components/smart_thermostat/climate.py index 10fedce..6a93a2c 100644 --- a/custom_components/smart_thermostat/climate.py +++ b/custom_components/smart_thermostat/climate.py @@ -32,6 +32,7 @@ SERVICE_SET_VALUE, DOMAIN as NUMBER_DOMAIN ) +from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN from homeassistant.core import DOMAIN as HA_DOMAIN, CoreState, callback from homeassistant.util import slugify import homeassistant.helpers.config_validation as cv @@ -491,6 +492,9 @@ def unique_id(self): """Return a unique ID.""" return self._unique_id + def _get_number_entity_domain(self, entity_id): + return INPUT_NUMBER_DOMAIN if "input_number" in entity_id else NUMBER_DOMAIN + @property def precision(self): """Return the precision of the system.""" @@ -725,13 +729,19 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: ATTR_VALUE: self._control_output} _LOGGER.debug("%s: Set heater to %s from async_set_hvac_mode(%s)", self.entity_id, self._control_output, hvac_mode) - await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + await self.hass.services.async_call( + self._get_number_entity_domain(self._heater_entity_id), + SERVICE_SET_VALUE, + data) if self._cooler_entity_id is not None: data = {ATTR_ENTITY_ID: self._cooler_entity_id, ATTR_VALUE: self._control_output} _LOGGER.debug("%s: Set cooler to %s from async_set_hvac_mode(%s)", self.entity_id, self._control_output, hvac_mode) - await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + await self.hass.services.async_call( + self._get_number_entity_domain(self._cooler_entity_id), + SERVICE_SET_VALUE, + data) # Clear the samples to avoid integrating the off period self._previous_temp = None self._previous_temp_time = None @@ -929,11 +939,17 @@ async def _async_control_heating(self, time_func=None, calc_pid=False): self._control_output = 0 data = {ATTR_ENTITY_ID: self._heater_entity_id, ATTR_VALUE: self._control_output} - await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + await self.hass.services.async_call( + self._get_number_entity_domain(self._heater_entity_id), + SERVICE_SET_VALUE, + data) if self._cooler_entity_id is not None: data = {ATTR_ENTITY_ID: self._cooler_entity_id, ATTR_VALUE: self._control_output} - await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + await self.hass.services.async_call( + self._get_number_entity_domain(self._cooler_entity_id), + SERVICE_SET_VALUE, + data) self.async_write_ha_state() return @@ -1000,7 +1016,10 @@ async def _async_heater_turn_off(self, force=False): self.heater_or_cooler_entity, 0) # self.hass.states.async_set(self._heater_entity_id, self._control_output) data = {ATTR_ENTITY_ID: self.heater_or_cooler_entity, ATTR_VALUE: 0} - await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + await self.hass.services.async_call( + self._get_number_entity_domain(self.heater_or_cooler_entity), + SERVICE_SET_VALUE, + data) async def async_set_preset_mode(self, preset_mode: str): """Set new preset mode. @@ -1123,7 +1142,10 @@ async def set_control_value(self): # self.hass.states.async_set(self._heater_entity_id, self._control_output) data = {ATTR_ENTITY_ID: self.heater_or_cooler_entity, ATTR_VALUE: abs(self._control_output)} - await self.hass.services.async_call(NUMBER_DOMAIN, SERVICE_SET_VALUE, data) + await self.hass.services.async_call( + self._get_number_entity_domain(self.heater_or_cooler_entity), + SERVICE_SET_VALUE, + data) async def pwm_switch(self, time_on, time_off, time_passed): """turn off and on the heater proportionally to control_value."""