hass-core/homeassistant/components/schluter/climate.py
Ville Skyttä b4bac0f7a0
Exception chaining and wrapping improvements (#39320)
* Remove unnecessary exception re-wraps

* Preserve exception chains on re-raise

We slap "from cause" to almost all possible cases here. In some cases it
could conceivably be better to do "from None" if we really want to hide
the cause. However those should be in the minority, and "from cause"
should be an improvement over the corresponding raise without a "from"
in all cases anyway.

The only case where we raise from None here is in plex, where the
exception for an original invalid SSL cert is not the root cause for
failure to validate a newly fetched one.

Follow local convention on exception variable names if there is a
consistent one, otherwise `err` to match with majority of codebase.

* Fix mistaken re-wrap in homematicip_cloud/hap.py

Missed the difference between HmipConnectionError and
HmipcConnectionError.

* Do not hide original error on plex new cert validation error

Original is not the cause for the new one, but showing old in the
traceback is useful nevertheless.
2020-08-28 13:50:32 +02:00

168 lines
5.2 KiB
Python

"""Support for Schluter thermostats."""
import logging
from requests import RequestException
import voluptuous as vol
from homeassistant.components.climate import (
PLATFORM_SCHEMA,
SCAN_INTERVAL,
TEMP_CELSIUS,
ClimateEntity,
)
from homeassistant.components.climate.const import (
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
HVAC_MODE_HEAT,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, CONF_SCAN_INTERVAL
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from . import DATA_SCHLUTER_API, DATA_SCHLUTER_SESSION, DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{vol.Optional(CONF_SCAN_INTERVAL): vol.All(vol.Coerce(int), vol.Range(min=1))}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Schluter thermostats."""
if discovery_info is None:
return
session_id = hass.data[DOMAIN][DATA_SCHLUTER_SESSION]
api = hass.data[DOMAIN][DATA_SCHLUTER_API]
async def async_update_data():
try:
thermostats = await hass.async_add_executor_job(
api.get_thermostats, session_id
)
except RequestException as err:
raise UpdateFailed(f"Error communicating with Schluter API: {err}") from err
if thermostats is None:
return {}
return {thermo.serial_number: thermo for thermo in thermostats}
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="schluter",
update_method=async_update_data,
update_interval=SCAN_INTERVAL,
)
await coordinator.async_refresh()
async_add_entities(
SchluterThermostat(coordinator, serial_number, api, session_id)
for serial_number, thermostat in coordinator.data.items()
)
class SchluterThermostat(ClimateEntity):
"""Representation of a Schluter thermostat."""
def __init__(self, coordinator, serial_number, api, session_id):
"""Initialize the thermostat."""
self._coordinator = coordinator
self._serial_number = serial_number
self._api = api
self._session_id = session_id
self._support_flags = SUPPORT_TARGET_TEMPERATURE
@property
def available(self):
"""Return if thermostat is available."""
return self._coordinator.last_update_success
@property
def should_poll(self):
"""Return if platform should poll."""
return False
@property
def supported_features(self):
"""Return the list of supported features."""
return self._support_flags
@property
def unique_id(self):
"""Return unique ID for this device."""
return self._serial_number
@property
def name(self):
"""Return the name of the thermostat."""
return self._coordinator.data[self._serial_number].name
@property
def temperature_unit(self):
"""Schluter API always uses celsius."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._coordinator.data[self._serial_number].temperature
@property
def hvac_mode(self):
"""Return current mode. Only heat available for floor thermostat."""
return HVAC_MODE_HEAT
@property
def hvac_action(self):
"""Return current operation. Can only be heating or idle."""
return (
CURRENT_HVAC_HEAT
if self._coordinator.data[self._serial_number].is_heating
else CURRENT_HVAC_IDLE
)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._coordinator.data[self._serial_number].set_point_temp
@property
def hvac_modes(self):
"""List of available operation modes."""
return [HVAC_MODE_HEAT]
@property
def min_temp(self):
"""Identify min_temp in Schluter API."""
return self._coordinator.data[self._serial_number].min_temp
@property
def max_temp(self):
"""Identify max_temp in Schluter API."""
return self._coordinator.data[self._serial_number].max_temp
async def async_set_hvac_mode(self, hvac_mode):
"""Mode is always heating, so do nothing."""
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = None
target_temp = kwargs.get(ATTR_TEMPERATURE)
serial_number = self._coordinator.data[self._serial_number].serial_number
_LOGGER.debug("Setting thermostat temperature: %s", target_temp)
try:
if target_temp is not None:
self._api.set_temperature(self._session_id, serial_number, target_temp)
except RequestException as ex:
_LOGGER.error("An error occurred while setting temperature: %s", ex)
async def async_added_to_hass(self):
"""When entity is added to hass."""
self._coordinator.async_add_listener(self.async_write_ha_state)
async def async_will_remove_from_hass(self):
"""When entity will be removed from hass."""
self._coordinator.async_remove_listener(self.async_write_ha_state)