"""Thermostat device classes.""" from __future__ import annotations import logging from typing import TYPE_CHECKING from pyvesync.base_devices.thermostat_base import VeSyncThermostat from pyvesync.const import ( ThermostatConst, ThermostatEcoTypes, ThermostatFanModes, ThermostatHoldStatus, ThermostatWorkModes, ) from pyvesync.models.thermostat_models import ( ResultThermostatDetails, ThermostatMinorDetails, ) from pyvesync.utils.device_mixins import BypassV2Mixin, process_bypassv2_result from pyvesync.utils.helpers import Helpers, Validators if TYPE_CHECKING: from pyvesync import VeSync from pyvesync.device_map import ThermostatMap from pyvesync.models.vesync_models import ResponseDeviceDetailsModel _LOGGER = logging.getLogger(__name__) class VeSyncAuraThermostat(BypassV2Mixin, VeSyncThermostat): """VeSync Aura Thermostat.""" def __init__( self, details: ResponseDeviceDetailsModel, manager: VeSync, feature_map: ThermostatMap, ) -> None: """Initialize VeSync Aura Thermostat.""" super().__init__(details, manager, feature_map) def _process_details(self, details: ResultThermostatDetails) -> None: """Internal method to process thermostat details.""" if ResultThermostatDetails.supportMode is not None: self.supported_work_modes = [ ThermostatWorkModes(mode) for mode in ResultThermostatDetails.supportMode ] self.state.work_mode = ThermostatConst.WorkMode(details.workMode) self.state.work_status = ThermostatConst.WorkStatus(details.workStatus) self.state.fan_status = ThermostatConst.FanStatus(details.fanStatus) self.state.fan_mode = ThermostatConst.FanMode(details.fanMode) self.state.temperature_unit = details.tempUnit self.state.temperature = details.temperature self.state.humidity = details.humidity self.state.heat_to_temp = details.heatToTemp self.state.cool_to_temp = details.coolToTemp self.state.deadband = details.deadband self.state.lock_status = details.lockStatus self.state.schedule_or_hold = ThermostatConst.ScheduleOrHoldOption( details.scheduleOrHold ) self.state.hold_end_time = details.holdEndTime self.state.hold_option = ThermostatConst.HoldOption(details.holdOption) self.state.eco_type = ThermostatConst.EcoType(details.ecoType) self.state.alert_status = details.alertStatus self.state.routine_running_id = details.routineRunningId self.state.routines = details.routines async def get_details(self) -> None: """Get the details of the thermostat.""" r_dict = await self.call_bypassv2_api('getTsStatus') r_model = process_bypassv2_result( self, _LOGGER, 'get_details', r_dict, ResultThermostatDetails ) if r_model is None: return self._process_details(r_model) async def get_configuration(self) -> None: """Get configuration or 'minor details'.""" r_dict = await self.call_bypassv2_api('getTsMinorInfo') result = process_bypassv2_result( self, _LOGGER, 'get_configuration', r_dict, ThermostatMinorDetails ) if result is None: return self.state.configuration = result async def _set_hold_status_api( self, key: str | None = None, temp: float | None = None, hold_status: ThermostatHoldStatus | None = None, ) -> bool: """Internal function to call the 'setHoldStatus' API.""" if key is None and temp is None and hold_status is None: _LOGGER.debug('At least one of key, temp, or hold_status must be provided.') return False payload_data: dict[str, str | float] = {} if hold_status is not None and hold_status.value == ThermostatHoldStatus.CANCEL: payload_data = {'holdStatus': ThermostatHoldStatus.CANCEL} else: if key is None or temp is None: _LOGGER.debug('Either key or temp must be provided.') return False payload_data = {key: temp, 'holdStatus': ThermostatHoldStatus.SET.value} r_dict = await self.call_bypassv2_api('setHoldStatus', data=payload_data) result = Helpers.process_dev_response(_LOGGER, 'setHoldStatus', self, r_dict) return bool(result) async def set_temp_point(self, temperature: float) -> bool: return await self._set_hold_status_api('setTempPoint', temperature) async def cancel_hold(self) -> bool: """Cancel hold.""" return await self._set_hold_status_api(hold_status=ThermostatHoldStatus.CANCEL) async def set_cool_to_temp(self, temperature: float) -> bool: """Set cool to temperature.""" return await self._set_hold_status_api('coolToTemp', temperature) async def set_heat_to_temp(self, temperature: float) -> bool: """Set heat to temperature.""" return await self._set_hold_status_api('heatToTemp', temperature) async def set_mode(self, mode: ThermostatWorkModes) -> bool: """Set thermostat mode.""" if mode not in self.supported_work_modes: _LOGGER.debug('Invalid mode: %s', mode) return False payload_data = {'tsMode': mode} r_dict = await self.call_bypassv2_api('setTsMode', data=payload_data) result = Helpers.process_dev_response(_LOGGER, 'setTsMode', self, r_dict) return bool(result) async def set_fan_mode(self, mode: ThermostatFanModes) -> bool: """Set thermostat fan mode.""" if mode not in self.fan_modes: _LOGGER.debug('Invalid fan mode: %s', mode) return False payload_data = {'fanMode': mode.value} r_dict = await self.call_bypassv2_api('setFanMode', data=payload_data) result = Helpers.process_dev_response(_LOGGER, 'setFanMode', self, r_dict) return bool(result) async def toggle_lock(self, toggle: bool, pin: int | str | None = None) -> bool: """Toggle thermostat lock status.""" if toggle is True and pin is None: _LOGGER.debug('PIN required to lock the thermostat.') return False payload_data: dict[str, bool | str] = {'lockStatus': toggle} if toggle is True: pin_int = int(pin) if isinstance(pin, str) else pin if not Validators.validate_range(pin_int, 0, 9999): _LOGGER.debug('PIN must be between 0 and 9999.') return False payload_data['lockPinCode'] = f'{pin_int:0>4}' r_dict = await self.call_bypassv2_api('setLockStatus', data=payload_data) result = Helpers.process_dev_response(_LOGGER, 'toggle_lock_status', self, r_dict) return bool(result) async def set_eco_type(self, eco_type: ThermostatEcoTypes) -> bool: """Set thermostat eco type.""" if eco_type not in self.eco_types: _LOGGER.debug('Invalid eco type: %s', eco_type) return False payload_data = {'ecoType': eco_type.value} r_dict = await self.call_bypassv2_api('setECOType', data=payload_data) result = Helpers.process_dev_response(_LOGGER, 'setEcoType', self, r_dict) return bool(result)