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

Issue #690 - VTherm don't follow underlying change with lastSeen activated #732

Merged
Show file tree
Hide file tree
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
36 changes: 21 additions & 15 deletions custom_components/versatile_thermostat/base_thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"temperature_slope",
"max_on_percent",
"have_valve_regulation",
"last_change_time_from_vtherm",
}
)
)
Expand Down Expand Up @@ -219,7 +220,9 @@ def __init__(

self._current_tz = dt_util.get_time_zone(self._hass.config.time_zone)

self._last_change_time = None
# Last change time is the datetime of the last change sent by VTherm to the device
# it is used in `over_cliamte` when a state have change from underlying to avoid loops
self._last_change_time_from_vtherm = None

self._underlyings: list[T] = []

Expand Down Expand Up @@ -749,14 +752,7 @@ async def async_startup(self, central_configuration):

self.hass.create_task(self._check_initial_state())

self.reset_last_change_time()

# if self.hass.state == CoreState.running:
# await _async_startup_internal()
# else:
# self.hass.bus.async_listen_once(
# EVENT_HOMEASSISTANT_START, _async_startup_internal
# )
self.reset_last_change_time_from_vtherm()

def init_underlyings(self):
"""Initialize all underlyings. Should be overriden if necessary"""
Expand Down Expand Up @@ -1223,7 +1219,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode, need_control_heating=Tr
return

def save_state():
self.reset_last_change_time()
self.reset_last_change_time_from_vtherm()
self.update_custom_attributes()
self.async_write_ha_state()
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
Expand Down Expand Up @@ -1355,12 +1351,14 @@ async def _async_set_preset_mode_internal(
if self._attr_preset_mode != old_preset_mode:
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})

def reset_last_change_time(
def reset_last_change_time_from_vtherm(
self, old_preset_mode: str | None = None
): # pylint: disable=unused-argument
"""Reset to now the last change time"""
self._last_change_time = self.now
_LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time)
self._last_change_time_from_vtherm = self.now
_LOGGER.debug(
"%s - last_change_time is now %s", self, self._last_change_time_from_vtherm
)

def reset_last_temperature_time(self, old_preset_mode: str | None = None):
"""Reset to now the last temperature time if conditions are satisfied"""
Expand Down Expand Up @@ -1460,7 +1458,7 @@ async def async_set_temperature(self, **kwargs):
await self._async_internal_set_temperature(temperature)
self._attr_preset_mode = PRESET_NONE
self.recalculate()
self.reset_last_change_time()
self.reset_last_change_time_from_vtherm()
await self.async_control_heating(force=True)

async def _async_internal_set_temperature(self, temperature: float):
Expand Down Expand Up @@ -1529,7 +1527,8 @@ async def _async_last_seen_temperature_changed(self, event: Event):
self._last_temperature_measure = self.get_last_updated_date_or_now(
new_state
)
self.reset_last_change_time()
# issue 690 - don't reset the last change time on lastSeen
# self.reset_last_change_time_from_vtherm()
_LOGGER.debug(
"%s - new last_temperature_measure is now: %s",
self,
Expand Down Expand Up @@ -2693,6 +2692,13 @@ def update_custom_attributes(self):
"hvac_off_reason": self.hvac_off_reason,
"max_on_percent": self._max_on_percent,
"have_valve_regulation": self.have_valve_regulation,
"last_change_time_from_vtherm": (
self._last_change_time_from_vtherm.astimezone(
self._current_tz
).isoformat()
if self._last_change_time_from_vtherm is not None
else None
),
}

_LOGGER.debug(
Expand Down
8 changes: 5 additions & 3 deletions custom_components/versatile_thermostat/thermostat_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ async def end_climate_changed(changes: bool):
_LOGGER.debug(
"%s - last_change_time=%s old_state_date_changed=%s old_state_date_updated=%s new_state_date_changed=%s new_state_date_updated=%s",
self,
self._last_change_time,
self._last_change_time_from_vtherm,
old_state_date_changed,
old_state_date_updated,
new_state_date_changed,
Expand Down Expand Up @@ -809,8 +809,10 @@ async def end_climate_changed(changes: bool):
# Filter new state when received just after a change from VTherm
# Issue #120 - Some TRV are changing target temperature a very long time (6 sec) after the change.
# In that case a loop is possible if a user change multiple times during this 6 sec.
if new_state_date_updated and self._last_change_time:
delta = (new_state_date_updated - self._last_change_time).total_seconds()
if new_state_date_updated and self._last_change_time_from_vtherm:
delta = (
new_state_date_updated - self._last_change_time_from_vtherm
).total_seconds()
if delta < 10:
_LOGGER.info(
"%s - underlying event is received less than 10 sec after command. Forget it to avoid loop",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/versatile_thermostat/underlyings.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
self._cancel_cycle()

if self.hvac_mode != hvac_mode:
super().set_hvac_mode(hvac_mode)
await super().set_hvac_mode(hvac_mode)
return True
else:
return False
Expand Down
4 changes: 4 additions & 0 deletions documentation/en/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,5 +271,9 @@ The custom attributes are as follows:
| ``auto_start_stop_enable`` | Indicates if the VTherm is allowed to auto start/stop |
| ``auto_start_stop_level`` | Indicates the auto start/stop level |
| ``hvac_off_reason`` | Indicates the reason for the thermostat's off state (hvac_off). It can be Window, Auto-start/stop, or Manual |
| ``last_change_time_from_vtherm`` | The date and time of the last change done by VTherm |
| ``nb_device_actives`` | The number of underlying devices seen as active |
| ``device_actives`` | The list of underlying devices seen as active |


These attributes will be requested when you need assistance.
3 changes: 3 additions & 0 deletions documentation/fr/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,8 @@ Les attributs personnalisés sont les suivants :
| ``auto_start_stop_enable`` | Indique si le VTherm est autorisé à s'auto démarrer/arrêter |
| ``auto_start_stop_level`` | Indique le niveau d'auto start/stop |
| ``hvac_off_reason`` | Indique la raison de l'arrêt (hvac_off) du VTherm. Ce peut être Window, Auto-start/stop ou Manuel |
| ``last_change_time_from_vtherm`` | La date/heure du dernier changement fait par VTherm |
| ``nb_device_actives`` | Le nombre de devices sous-jacents actuellement vus comme actifs |
| ``device_actives`` | La liste des devices sous-jacents actuellement vus comme actifs |

Ces attributs vous seront demandés lors d'une demande d'aide.
4 changes: 2 additions & 2 deletions tests/test_auto_start_stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ async def test_auto_start_stop_fast_heat_window(
now: datetime = datetime.now(tz=tz)

# 2. Set mode to Heat and preset to Comfort and close the window
send_window_change_event(vtherm, False, False, now, False)
await send_window_change_event(vtherm, False, False, now, False)
await send_presence_change_event(vtherm, True, False, now)
await send_temperature_change_event(vtherm, 18, now, True)
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
Expand Down Expand Up @@ -1474,7 +1474,7 @@ async def test_auto_start_stop_fast_heat_window_mixed(
now: datetime = datetime.now(tz=tz)

# 2. Set mode to Heat and preset to Comfort and close the window
send_window_change_event(vtherm, False, False, now, False)
await send_window_change_event(vtherm, False, False, now, False)
await send_presence_change_event(vtherm, True, False, now)
await send_temperature_change_event(vtherm, 18, now, True)
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
Expand Down
6 changes: 6 additions & 0 deletions tests/test_last_seen.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state)
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert entity.hvac_mode == HVACMode.HEAT

last_change_time_from_vtherm = entity._last_change_time_from_vtherm

# 2. activate security feature when date is expired
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
Expand Down Expand Up @@ -128,9 +130,13 @@ async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state)

assert mock_heater_on.call_count == 1

assert entity._last_change_time_from_vtherm == last_change_time_from_vtherm

# 3. change the last seen sensor
event_timestamp = now - timedelta(minutes=4)
await send_last_seen_temperature_change_event(entity, event_timestamp)
assert entity.security_state is False
assert entity.preset_mode is PRESET_COMFORT
assert entity._last_temperature_measure == event_timestamp

assert entity._last_change_time_from_vtherm == last_change_time_from_vtherm
4 changes: 2 additions & 2 deletions tests/test_overclimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ async def test_manual_hvac_off_should_take_the_lead_over_window(
now: datetime = datetime.now(tz=tz)

# 1. Set mode to Heat and preset to Comfort and close the window
send_window_change_event(vtherm, False, False, now, False)
await send_window_change_event(vtherm, False, False, now, False)
await send_presence_change_event(vtherm, True, False, now)
await send_temperature_change_event(vtherm, 18, now, True)
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
Expand Down Expand Up @@ -1123,7 +1123,7 @@ async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
now: datetime = datetime.now(tz=tz)

# 1. Set mode to Heat and preset to Comfort
send_window_change_event(vtherm, False, False, now, False)
await send_window_change_event(vtherm, False, False, now, False)
await send_presence_change_event(vtherm, True, False, now)
await send_temperature_change_event(vtherm, 18, now, True)
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ async def test_window_management_time_enough(
assert entity.preset_mode is PRESET_BOOST
assert entity.hvac_mode is HVACMode.HEAT
assert entity._saved_hvac_mode is HVACMode.HEAT # No change
assert entity.hvac_off_reason == None
assert entity.hvac_off_reason is None

# Clean the entity
entity.remove_thermostat()
Expand Down
Loading