179 lines
6.7 KiB
Python
179 lines
6.7 KiB
Python
"""State container for Versatile Thermostat.
|
|
|
|
This lightweight data class is intended to group together the minimal
|
|
public state we may want to persist, serialize, expose via services
|
|
or exchange between managers (for example during algorithms decisions
|
|
or for debug tooling).
|
|
|
|
Attributes:
|
|
hvac_mode: Current HVAC mode of the thermostat (e.g. 'heat', 'off', 'cool', 'sleep').
|
|
target_temperature: The active target temperature (can be None when mode does not use a temperature, e.g. 'off').
|
|
preset: The current preset name (e.g. 'eco', 'comfort', 'boost', 'away') or None if not in a preset.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional, Any
|
|
|
|
from .vtherm_hvac_mode import VThermHvacMode
|
|
from .vtherm_preset import VThermPreset
|
|
|
|
|
|
class VThermState:
|
|
"""Simple state snapshot for a VTherm.
|
|
|
|
The object is mutable; use set_state for a bulk update or assign
|
|
attributes individually via properties. to_dict / from_dict help (de)serialization.
|
|
"""
|
|
|
|
def __init__(self, hvac_mode: Any, target_temperature: Optional[float] = None, preset: Optional[VThermPreset] = None) -> None:
|
|
if preset is not None and not isinstance(preset, str):
|
|
raise ValueError(f"Invalid preset: {preset}. Should be an instance of VThermPreset.")
|
|
|
|
self._hvac_mode: VThermHvacMode = hvac_mode if isinstance(hvac_mode, VThermHvacMode) else VThermHvacMode(str(hvac_mode))
|
|
self._target_temperature: Optional[float] = target_temperature
|
|
self._preset: Optional[VThermPreset] = preset
|
|
self._is_hvac_mode_changed: bool = True
|
|
self._is_target_temperature_changed: bool = True
|
|
self._is_preset_changed: bool = True
|
|
|
|
def set_state(
|
|
self,
|
|
hvac_mode: Optional[VThermHvacMode] = None,
|
|
target_temperature: Optional[float] = None,
|
|
preset: Optional[VThermPreset] = None,
|
|
) -> None:
|
|
"""Update only the attributes provided (not None).
|
|
|
|
Args:
|
|
hvac_mode: New HVAC mode to set (if not None).
|
|
target_temperature: New target temperature (if not None).
|
|
preset: New preset (if not None).
|
|
"""
|
|
if hvac_mode is not None:
|
|
self.set_hvac_mode(hvac_mode)
|
|
if target_temperature is not None:
|
|
self.set_target_temperature(target_temperature)
|
|
if preset is not None:
|
|
self.set_preset(preset)
|
|
|
|
def set_hvac_mode(self, hvac_mode: VThermHvacMode) -> None:
|
|
"""Set the HVAC mode only.
|
|
|
|
Args:
|
|
hvac_mode: New HVAC mode to set (e.g. 'heat', 'off', 'cool', 'sleep').
|
|
"""
|
|
if not isinstance(hvac_mode, VThermHvacMode):
|
|
hvac_mode = VThermHvacMode(str(hvac_mode))
|
|
|
|
self._is_hvac_mode_changed = self._is_hvac_mode_changed or self._hvac_mode != hvac_mode
|
|
self._hvac_mode = hvac_mode
|
|
|
|
def set_target_temperature(self, target_temperature: Optional[float]) -> None:
|
|
"""Set the target temperature only.
|
|
|
|
Args:
|
|
target_temperature: New target temperature to set, or None.
|
|
"""
|
|
self._is_target_temperature_changed = self._is_target_temperature_changed or self._target_temperature != target_temperature
|
|
self._target_temperature = target_temperature
|
|
|
|
def set_preset(self, preset: Optional[VThermPreset]) -> None:
|
|
"""Set the preset only.
|
|
|
|
Args:
|
|
preset: New preset name to set, or None.
|
|
"""
|
|
if preset is not None and not isinstance(preset, str):
|
|
raise ValueError(f"Invalid preset: {preset}. Should be an instance of VThermPreset.")
|
|
|
|
self._is_preset_changed = self._is_preset_changed or self._preset != preset
|
|
self._preset = preset
|
|
|
|
def force_changed(self) -> None:
|
|
"""Forcefully mark the state as changed."""
|
|
self._is_hvac_mode_changed = True
|
|
self._is_target_temperature_changed = True
|
|
self._is_preset_changed = True
|
|
|
|
@property
|
|
def hvac_mode(self) -> VThermHvacMode:
|
|
"""Get or set the current HVAC mode."""
|
|
return self._hvac_mode
|
|
|
|
@property
|
|
def target_temperature(self) -> Optional[float]:
|
|
"""Get or set the current target temperature."""
|
|
return self._target_temperature
|
|
|
|
@property
|
|
def preset(self) -> Optional[VThermPreset]:
|
|
"""Get or set the current preset."""
|
|
return self._preset
|
|
|
|
@property
|
|
def is_changed(self) -> bool:
|
|
"""Check if the state has changed."""
|
|
return self._is_hvac_mode_changed or self._is_target_temperature_changed or self._is_preset_changed
|
|
|
|
@property
|
|
def is_hvac_mode_changed(self) -> bool:
|
|
"""Check if the HVAC mode has changed."""
|
|
return self._is_hvac_mode_changed
|
|
|
|
@property
|
|
def is_target_temperature_changed(self) -> bool:
|
|
"""Check if the target temperature has changed."""
|
|
return self._is_target_temperature_changed
|
|
|
|
@property
|
|
def is_preset_changed(self) -> bool:
|
|
"""Check if the preset has changed."""
|
|
return self._is_preset_changed
|
|
|
|
def reset_changed(self) -> None:
|
|
"""Reset the changed state."""
|
|
self._is_hvac_mode_changed = False
|
|
self._is_target_temperature_changed = False
|
|
self._is_preset_changed = False
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert the state to a dictionary."""
|
|
return {
|
|
"hvac_mode": str(self._hvac_mode),
|
|
"target_temperature": self._target_temperature,
|
|
"preset": str(self._preset),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict[str, Any]) -> "VThermState":
|
|
"""Create a VThermState from a dictionary."""
|
|
hvac_mode = VThermHvacMode(data["hvac_mode"])
|
|
target_temperature = data["target_temperature"]
|
|
preset = VThermPreset(data["preset"]) if data["preset"] else None
|
|
state = cls(hvac_mode, target_temperature, preset)
|
|
return state
|
|
|
|
def __str__(self) -> str:
|
|
"""Return a human readable representation of the state."""
|
|
return f"VThermState(" f"hvac_mode={self._hvac_mode}, " f"target_temperature={self._target_temperature}, " f"preset={self._preset}, " f"is_changed={self.is_changed})"
|
|
|
|
__repr__ = __str__
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
"""Compare two VThermState instances for equality.
|
|
|
|
Args:
|
|
other: Another object to compare with.
|
|
|
|
Returns:
|
|
True if both states have the same hvac_mode, target_temperature, and preset.
|
|
"""
|
|
if not isinstance(other, VThermState):
|
|
return False
|
|
|
|
return self._hvac_mode == other._hvac_mode and self._target_temperature == other._target_temperature and self._preset == other._preset
|
|
|
|
def __ne__(self, other: object) -> bool:
|
|
"""Compare two VThermState instances for inequality."""
|
|
return not self.__eq__(other)
|