Source code for aioafero.v1.controllers.climate

"""Base controller for climate devices."""

from typing import TypeVar

from aioafero import device
from aioafero.device import AferoCapability, AferoDevice, AferoResource, AferoState
from aioafero.util import process_function
from aioafero.v1.models import features

from .base import BaseResourcesController

AferoResourceT = TypeVar("AferoResourceT", bound=AferoResource)


TARGET_INSTANCE_MAPPING = {
    "cooling-target": "target_temperature_cooling",
    "heating-target": "target_temperature_heating",
    "auto-cooling-target": "target_temperature_auto_cooling",
    "auto-heating-target": "target_temperature_auto_heating",
    "safety-mode-max-temp": "safety_max_temp",
    "safety-mode-min-temp": "safety_min_temp",
}


[docs] def generate_target_temp( func_def: dict, state: AferoState ) -> features.TargetTemperatureFeature: """Determine the target temp based on the function definition.""" return features.TargetTemperatureFeature( value=round(state.value, 1), step=func_def["range"]["step"], min=func_def["range"]["min"], max=func_def["range"]["max"], instance=state.functionInstance, )
[docs] def generate_target_temp_capability( capability: AferoCapability, state: AferoState ) -> features.TargetTemperatureFeature: """Determine the target temp based on the function definition.""" return features.TargetTemperatureFeature( value=round(state.value, 1), step=capability.options["range"]["step"], min=capability.options["range"]["min"], max=capability.options["range"]["max"], instance=state.functionInstance, )
[docs] class ClimateController(BaseResourcesController[AferoResourceT]): """Base controller for climate devices."""
[docs] async def initialize_climate_elem(self, afero_device: AferoDevice) -> dict: """Initialize the climate elements of a device.""" climate_data = { "available": False, "current_temperature": None, "hvac_mode": None, "target_temperature_cooling": None, "target_temperature_heating": None, "target_temperature_auto_cooling": None, "target_temperature_auto_heating": None, "safety_max_temp": None, "safety_min_temp": None, "numbers": {}, "selects": {}, "sensors": {}, "binary_sensors": {}, } for state in afero_device.states: if state.functionClass == "temperature": if state.functionInstance == "current-temp": climate_data["current_temperature"] = ( features.CurrentTemperatureFeature( temperature=round(state.value, 1), function_class=state.functionClass, function_instance=state.functionInstance, ) ) else: capability_def = device.get_capability_from_device( afero_device.capabilities, state.functionClass, state.functionInstance, ) if capability_def: target_data = generate_target_temp_capability( capability_def, state ) else: # @TODO - This exists as we do not have data dumps with capabilities # for all devices. We should remove this fallback once we do func_def = device.get_function_from_device( afero_device.functions, state.functionClass, state.functionInstance, ) target_data = generate_target_temp(func_def["values"][0], state) if state.functionInstance in TARGET_INSTANCE_MAPPING: climate_data[ TARGET_INSTANCE_MAPPING[state.functionInstance] ] = target_data else: self._logger.warning("Found unknown temp instance, %s", state) elif state.functionClass == "mode": all_modes = set(process_function(afero_device.functions, "mode")) climate_data["hvac_mode"] = features.HVACModeFeature( mode=state.value, previous_mode=state.value, modes=all_modes, supported_modes=all_modes, ) elif state.functionClass == "available": climate_data["available"] = state.value elif select := await self.initialize_select(afero_device.functions, state): climate_data["selects"][select[0]] = select[1] return climate_data
[docs] async def update_climate_elem(self, afero_device: AferoDevice) -> set: """Update the climate elements of a device.""" updated_keys = set() cur_item = self.get_device(afero_device.id) for state in afero_device.states: if state.functionClass == "available": if cur_item.available != state.value: cur_item.available = state.value updated_keys.add("available") elif state.functionClass == "temperature": if state.functionInstance == "current-temp": temp_value = cur_item.current_temperature.temperature rounded_val = round(state.value, 1) if temp_value != rounded_val: cur_item.current_temperature.temperature = rounded_val updated_keys.add(f"temperature-{state.functionInstance}") elif state.functionInstance in TARGET_INSTANCE_MAPPING: temp_item = getattr( cur_item, TARGET_INSTANCE_MAPPING.get(state.functionInstance), None, ) if temp_item and temp_item.value != state.value: temp_item.value = state.value updated_keys.add(f"temperature-{state.functionInstance}") elif state.functionClass == "mode": if cur_item.hvac_mode and cur_item.hvac_mode.mode != state.value: # We only want to update the previous mode when we are in heat or cool if cur_item.hvac_mode.mode in ["cool", "heat"]: cur_item.hvac_mode.previous_mode = cur_item.hvac_mode.mode cur_item.hvac_mode.mode = state.value updated_keys.add(state.functionClass) elif (update_key := await self.update_number(state, cur_item)) or ( update_key := await self.update_select(state, cur_item) ): updated_keys.add(update_key) return updated_keys
[docs] async def set_climate_state(self, device_id: str, update_obj, **kwargs) -> None: """Set climate state.""" cur_item = self.get_device(device_id) temperature_kwargs = { "target_temperature_auto_heating": "target_temperature_auto_heating", "target_temperature_auto_cooling": "target_temperature_auto_cooling", "target_temperature_heating": "target_temperature_heating", "target_temperature_cooling": "target_temperature_cooling", "safety_min_temp": "safety_min_temp", "safety_max_temp": "safety_max_temp", } for kwarg_name, attr_name in temperature_kwargs.items(): temp_val = kwargs.get(kwarg_name) if temp_val is not None: cur_temp_feature = getattr(cur_item, attr_name, None) setattr( update_obj, attr_name, features.TargetTemperatureFeature( value=temp_val, min=cur_temp_feature.min, max=cur_temp_feature.max, step=cur_temp_feature.step, instance=cur_temp_feature.instance, ), ) await self.update(device_id, obj_in=update_obj)