Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Optimize state updates and ensure ON_OFF feature support for HVACMode.OFF #1

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 93 additions & 45 deletions custom_components/climate_template/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,31 @@ def __init__(self, hass: HomeAssistant, config: ConfigType):
self._unit_of_measurement = hass.config.units.temperature_unit
self._attr_supported_features = 0

if HVACMode.OFF in config[CONF_MODE_LIST] and len(config[CONF_MODE_LIST]) > 1:
if not hasattr(ClimateEntityFeature, "ON_OFF"):
ON_OFF_FEATURE = 1 << 8
else:
ON_OFF_FEATURE = ClimateEntityFeature.ON_OFF
self._attr_supported_features |= ON_OFF_FEATURE

# Dynamically add turn_on and turn_off methods if needed
async def async_turn_on(self):
"""Turn the climate device on."""
if HVACMode.OFF in self._attr_hvac_modes:
self._current_operation = next(
mode for mode in self._attr_hvac_modes if mode != HVACMode.OFF
)
self.async_write_ha_state()

async def async_turn_off(self):
"""Turn the climate device off."""
if HVACMode.OFF in self._attr_hvac_modes:
self._current_operation = HVACMode.OFF
self.async_write_ha_state()

self.async_turn_on = async_turn_on.__get__(self)
self.async_turn_off = async_turn_off.__get__(self)

self._attr_hvac_modes = config[CONF_MODE_LIST]
self._attr_fan_modes = config[CONF_FAN_MODE_LIST]
self._attr_preset_modes = config[CONF_PRESET_MODE_LIST]
Expand Down Expand Up @@ -539,51 +564,55 @@ def _update_max_humidity(self, humidity):
def _update_target_humidity(self, humidity):
if humidity not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
try:
self._target_humidity = float(humidity)
self.hass.async_create_task(
self.async_set_humidity(self._target_humidity)
)
new_humidity = float(humidity)
if (
new_humidity != self._target_humidity
): # Only update if there's a change
self._target_humidity = new_humidity
self.async_write_ha_state() # Update HA state without triggering an action
except ValueError:
_LOGGER.error("Could not parse target humidity from %s", humidity)

def _update_target_temp(self, temp):
if temp not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
try:
self._target_temp = float(temp)
self.hass.async_create_task(
self.async_set_temperature(**{ATTR_TEMPERATURE: self._target_temp})
)
# Update the internal state without triggering the set_temperature action
new_target_temp = float(temp)
if (
new_target_temp != self._target_temp
): # Only update if there's a change
self._target_temp = new_target_temp
self.async_write_ha_state() # Update the HA state without triggering an action
except ValueError:
_LOGGER.error("Could not parse temperature from %s", temp)

def _update_target_temp_high(self, temp):
if temp not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
try:
self._attr_target_temperature_high = float(temp)
self.hass.async_create_task(
self.async_set_temperature(
**{ATTR_TARGET_TEMP_HIGH: self._attr_target_temperature_high}
)
)
# Update the internal state without triggering the set_temperature action
new_target_temp_high = float(temp)
if new_target_temp_high != self._attr_target_temperature_high:
self._attr_target_temperature_high = new_target_temp_high
self.async_write_ha_state()
except ValueError:
_LOGGER.error("Could not parse temperature high from %s", temp)

def _update_target_temp_low(self, temp):
if temp not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
try:
self._attr_target_temperature_low = float(temp)
self.hass.async_create_task(
self.async_set_temperature(
**{ATTR_TARGET_TEMP_LOW: self._attr_target_temperature_low}
)
)
# Update the internal state without triggering the set_temperature action
new_target_temp_low = float(temp)
if new_target_temp_low != self._attr_target_temperature_low:
self._attr_target_temperature_low = new_target_temp_low
self.async_write_ha_state()
except ValueError:
_LOGGER.error("Could not parse temperature low from %s", temp)

def _update_hvac_mode(self, hvac_mode):
if hvac_mode in self._attr_hvac_modes:
self._current_operation = hvac_mode
self.hass.async_create_task(self.async_set_hvac_mode(hvac_mode))
if self._current_operation != hvac_mode: # Only update if there's a change
self._current_operation = hvac_mode
self.async_write_ha_state() # Update HA state without triggering an action
elif hvac_mode not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
_LOGGER.error(
"Received invalid hvac mode: %s. Expected: %s.",
Expand All @@ -593,8 +622,11 @@ def _update_hvac_mode(self, hvac_mode):

def _update_preset_mode(self, preset_mode):
if preset_mode in self._attr_preset_modes:
self._current_preset_mode = preset_mode
self.hass.async_create_task(self.async_set_preset_mode(preset_mode))
if (
self._current_preset_mode != preset_mode
): # Only update if there's a change
self._current_preset_mode = preset_mode
self.async_write_ha_state() # Update HA state without triggering an action
elif preset_mode not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
_LOGGER.error(
"Received invalid preset mode %s. Expected %s.",
Expand All @@ -604,8 +636,9 @@ def _update_preset_mode(self, preset_mode):

def _update_fan_mode(self, fan_mode):
if fan_mode in self._attr_fan_modes:
self._current_fan_mode = fan_mode
self.hass.async_create_task(self.async_set_fan_mode(fan_mode))
sayam93 marked this conversation as resolved.
Show resolved Hide resolved
if self._current_fan_mode != fan_mode: # Only update if there's a change
self._current_fan_mode = fan_mode
self.async_write_ha_state() # Update HA state without triggering an action
elif fan_mode not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
_LOGGER.error(
"Received invalid fan mode: %s. Expected: %s.",
Expand All @@ -615,10 +648,11 @@ def _update_fan_mode(self, fan_mode):

def _update_swing_mode(self, swing_mode):
if swing_mode in self._swing_modes_list:
# check swing mode actually changed
if self._current_swing_mode != swing_mode:
if (
self._current_swing_mode != swing_mode
): # Only update if there's a change
self._current_swing_mode = swing_mode
self.hass.async_create_task(self.async_set_swing_mode(swing_mode))
self.async_write_ha_state() # Update HA state without triggering an action
elif swing_mode not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
_LOGGER.error(
"Received invalid swing mode: %s. Expected: %s.",
Expand All @@ -631,8 +665,9 @@ def _update_hvac_action(self, hvac_action):
hvac_action in [member.value for member in HVACAction]
or hvac_action is None
):
if self._attr_hvac_action != hvac_action:
if self._attr_hvac_action != hvac_action: # Only update if there's a change
self._attr_hvac_action = hvac_action
self.async_write_ha_state() # Update HA state without triggering an action
elif hvac_action not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
_LOGGER.error(
"Received invalid hvac action: %s. Expected: %s.",
Expand Down Expand Up @@ -772,29 +807,42 @@ async def async_set_swing_mode(self, swing_mode: str) -> None:
)

async def async_set_temperature(self, **kwargs) -> None:
"""Set new target temperature."""
# handle optimistic mode
if kwargs.get(ATTR_HVAC_MODE, self._current_operation) == HVACMode.HEAT_COOL:
if self._target_temperature_high_template is None:
self._attr_target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
"""Set new target temperature explicitly triggered by user or automation."""
updated = False

if self._target_temperature_low_template is None:
self._attr_target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
if kwargs.get(ATTR_HVAC_MODE, self._current_operation) == HVACMode.HEAT_COOL:
# Explicitly update high and low target temperatures if provided
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)

if (
self._target_temperature_high_template
or self._target_temperature_low_template
high_temp is not None
and high_temp != self._attr_target_temperature_high
):
self.async_write_ha_state()
self._attr_target_temperature_high = high_temp
updated = True

if low_temp is not None and low_temp != self._attr_target_temperature_low:
self._attr_target_temperature_low = low_temp
updated = True

else:
if self._target_temperature_template is None:
self._target_temp = kwargs.get(ATTR_TEMPERATURE)
self.async_write_ha_state()
# Explicitly update single target temperature if provided
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is not None and temp != self._target_temp:
self._target_temp = temp
updated = True

# Update Home Assistant state if any changes occurred
if updated:
self.async_write_ha_state()

# set temperature calls can contain a new hvac mode.
# Handle potential HVAC mode change
if operation_mode := kwargs.get(ATTR_HVAC_MODE):
await self.async_set_hvac_mode(operation_mode)
if operation_mode != self._current_operation:
await self.async_set_hvac_mode(operation_mode)

# Run the set temperature script if defined
if self._set_temperature_script is not None:
await self._set_temperature_script.async_run(
run_variables={
Expand Down