Files
home-assistant-core/homeassistant/components/vicare/climate.py
T
Hans Oischinger 3d2ff841d3 Handle exceptions from PyViCare library (#28536)
* ViCare: Handle exceptions from PyViCare library (#28072)

Sometimes Viessmann server failures or other connection problems may
lead to exceptions thrown when updating data.

This commit handles those exceptions with some error logging and
makes sure that the component does not break completely in that case.

* Remove unneeded returns

* Remove unneeded returns
2019-11-06 22:46:18 +01:00

280 lines
9.3 KiB
Python

"""Viessmann ViCare climate device."""
import logging
import requests
import simplejson
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
PRESET_ECO,
PRESET_COMFORT,
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_AUTO,
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
)
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_WHOLE
from . import DOMAIN as VICARE_DOMAIN
from . import VICARE_API
from . import VICARE_NAME
from . import VICARE_HEATING_TYPE
from . import HeatingType
_LOGGER = logging.getLogger(__name__)
VICARE_MODE_DHW = "dhw"
VICARE_MODE_DHWANDHEATING = "dhwAndHeating"
VICARE_MODE_DHWANDHEATINGCOOLING = "dhwAndHeatingCooling"
VICARE_MODE_FORCEDREDUCED = "forcedReduced"
VICARE_MODE_FORCEDNORMAL = "forcedNormal"
VICARE_MODE_OFF = "standby"
VICARE_PROGRAM_ACTIVE = "active"
VICARE_PROGRAM_COMFORT = "comfort"
VICARE_PROGRAM_ECO = "eco"
VICARE_PROGRAM_EXTERNAL = "external"
VICARE_PROGRAM_HOLIDAY = "holiday"
VICARE_PROGRAM_NORMAL = "normal"
VICARE_PROGRAM_REDUCED = "reduced"
VICARE_PROGRAM_STANDBY = "standby"
VICARE_HOLD_MODE_AWAY = "away"
VICARE_HOLD_MODE_HOME = "home"
VICARE_HOLD_MODE_OFF = "off"
VICARE_TEMP_HEATING_MIN = 3
VICARE_TEMP_HEATING_MAX = 37
SUPPORT_FLAGS_HEATING = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
VICARE_TO_HA_HVAC_HEATING = {
VICARE_MODE_DHW: HVAC_MODE_OFF,
VICARE_MODE_DHWANDHEATING: HVAC_MODE_AUTO,
VICARE_MODE_DHWANDHEATINGCOOLING: HVAC_MODE_AUTO,
VICARE_MODE_FORCEDREDUCED: HVAC_MODE_OFF,
VICARE_MODE_FORCEDNORMAL: HVAC_MODE_HEAT,
VICARE_MODE_OFF: HVAC_MODE_OFF,
}
HA_TO_VICARE_HVAC_HEATING = {
HVAC_MODE_HEAT: VICARE_MODE_FORCEDNORMAL,
HVAC_MODE_OFF: VICARE_MODE_FORCEDREDUCED,
HVAC_MODE_AUTO: VICARE_MODE_DHWANDHEATING,
}
VICARE_TO_HA_PRESET_HEATING = {
VICARE_PROGRAM_COMFORT: PRESET_COMFORT,
VICARE_PROGRAM_ECO: PRESET_ECO,
}
HA_TO_VICARE_PRESET_HEATING = {
PRESET_COMFORT: VICARE_PROGRAM_COMFORT,
PRESET_ECO: VICARE_PROGRAM_ECO,
}
PYVICARE_ERROR = "error"
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the ViCare climate devices."""
if discovery_info is None:
return
vicare_api = hass.data[VICARE_DOMAIN][VICARE_API]
heating_type = hass.data[VICARE_DOMAIN][VICARE_HEATING_TYPE]
add_entities(
[
ViCareClimate(
f"{hass.data[VICARE_DOMAIN][VICARE_NAME]} Heating",
vicare_api,
heating_type,
)
]
)
class ViCareClimate(ClimateDevice):
"""Representation of the ViCare heating climate device."""
def __init__(self, name, api, heating_type):
"""Initialize the climate device."""
self._name = name
self._state = None
self._api = api
self._attributes = {}
self._target_temperature = None
self._current_mode = None
self._current_temperature = None
self._current_program = None
self._heating_type = heating_type
self._current_action = None
def update(self):
"""Let HA know there has been an update from the ViCare API."""
try:
_room_temperature = self._api.getRoomTemperature()
_supply_temperature = self._api.getSupplyTemperature()
if _room_temperature is not None and _room_temperature != PYVICARE_ERROR:
self._current_temperature = _room_temperature
elif _supply_temperature != PYVICARE_ERROR:
self._current_temperature = _supply_temperature
else:
self._current_temperature = None
self._current_program = self._api.getActiveProgram()
# The getCurrentDesiredTemperature call can yield 'error' (str) when the system is in standby
desired_temperature = self._api.getCurrentDesiredTemperature()
if desired_temperature == PYVICARE_ERROR:
desired_temperature = None
self._target_temperature = desired_temperature
self._current_mode = self._api.getActiveMode()
# Update the generic device attributes
self._attributes = {}
self._attributes["room_temperature"] = _room_temperature
self._attributes["supply_temperature"] = _supply_temperature
self._attributes["outside_temperature"] = self._api.getOutsideTemperature()
self._attributes["active_vicare_program"] = self._current_program
self._attributes["active_vicare_mode"] = self._current_mode
self._attributes["heating_curve_slope"] = self._api.getHeatingCurveSlope()
self._attributes["heating_curve_shift"] = self._api.getHeatingCurveShift()
self._attributes[
"month_since_last_service"
] = self._api.getMonthSinceLastService()
self._attributes["date_last_service"] = self._api.getLastServiceDate()
self._attributes["error_history"] = self._api.getErrorHistory()
self._attributes["active_error"] = self._api.getActiveError()
self._attributes[
"circulationpump_active"
] = self._api.getCirculationPumpActive()
# Update the specific device attributes
if self._heating_type == HeatingType.gas:
self._current_action = self._api.getBurnerActive()
self._attributes["burner_modulation"] = self._api.getBurnerModulation()
self._attributes[
"boiler_temperature"
] = self._api.getBoilerTemperature()
elif self._heating_type == HeatingType.heatpump:
self._current_action = self._api.getCompressorActive()
self._attributes[
"return_temperature"
] = self._api.getReturnTemperature()
except requests.exceptions.ConnectionError:
_LOGGER.error("Unable to retrieve data from ViCare server")
except simplejson.errors.JSONDecodeError:
_LOGGER.error("Unable to decode data from ViCare server")
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_HEATING
@property
def name(self):
"""Return the name of the climate device."""
return self._name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def hvac_mode(self):
"""Return current hvac mode."""
return VICARE_TO_HA_HVAC_HEATING.get(self._current_mode)
def set_hvac_mode(self, hvac_mode):
"""Set a new hvac mode on the ViCare API."""
vicare_mode = HA_TO_VICARE_HVAC_HEATING.get(hvac_mode)
if vicare_mode is None:
_LOGGER.error(
"Cannot set invalid vicare mode: %s / %s", hvac_mode, vicare_mode
)
return
_LOGGER.debug("Setting hvac mode to %s / %s", hvac_mode, vicare_mode)
self._api.setMode(vicare_mode)
@property
def hvac_modes(self):
"""Return the list of available hvac modes."""
return list(HA_TO_VICARE_HVAC_HEATING)
@property
def hvac_action(self):
"""Return the current hvac action."""
if self._current_action:
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
@property
def min_temp(self):
"""Return the minimum temperature."""
return VICARE_TEMP_HEATING_MIN
@property
def max_temp(self):
"""Return the maximum temperature."""
return VICARE_TEMP_HEATING_MAX
@property
def precision(self):
"""Return the precision of the system."""
return PRECISION_WHOLE
def set_temperature(self, **kwargs):
"""Set new target temperatures."""
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is not None:
self._api.setProgramTemperature(self._current_program, temp)
self._target_temperature = temp
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
return VICARE_TO_HA_PRESET_HEATING.get(self._current_program)
@property
def preset_modes(self):
"""Return the available preset mode."""
return list(VICARE_TO_HA_PRESET_HEATING)
def set_preset_mode(self, preset_mode):
"""Set new preset mode and deactivate any existing programs."""
vicare_program = HA_TO_VICARE_PRESET_HEATING.get(preset_mode)
if vicare_program is None:
_LOGGER.error(
"Cannot set invalid vicare program: %s / %s",
preset_mode,
vicare_program,
)
return
_LOGGER.debug("Setting preset to %s / %s", preset_mode, vicare_program)
self._api.deactivateProgram(self._current_program)
self._api.activateProgram(vicare_program)
@property
def device_state_attributes(self):
"""Show Device Attributes."""
return self._attributes