From 72bb0e97e0cfe6ccc05a13946df33e76e3fa5adb Mon Sep 17 00:00:00 2001 From: Dan Cinnamon Date: Thu, 25 Jan 2018 11:34:53 -0600 Subject: [PATCH] New venstar climate component (#11639) * New venstar climate component * Corrected items raised during the PR process. * Corrected flak8 issues. * Add support for venstar models without humidity control. * Changed the idle operation mode to off. Few other adjustments. * Minor changes (log messages, sorting, etc.) --- .coveragerc | 1 + homeassistant/components/climate/venstar.py | 267 ++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 271 insertions(+) create mode 100644 homeassistant/components/climate/venstar.py diff --git a/.coveragerc b/.coveragerc index de32f6d61a8..63805e6e428 100644 --- a/.coveragerc +++ b/.coveragerc @@ -327,6 +327,7 @@ omit = homeassistant/components/climate/radiotherm.py homeassistant/components/climate/sensibo.py homeassistant/components/climate/touchline.py + homeassistant/components/climate/venstar.py homeassistant/components/cover/garadget.py homeassistant/components/cover/homematic.py homeassistant/components/cover/knx.py diff --git a/homeassistant/components/climate/venstar.py b/homeassistant/components/climate/venstar.py new file mode 100644 index 00000000000..92e5c71b6c5 --- /dev/null +++ b/homeassistant/components/climate/venstar.py @@ -0,0 +1,267 @@ +""" +Support for Venstar WiFi Thermostats. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/climate.venstar/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.climate import ( + ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, + PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW, ClimateDevice) +from homeassistant.const import ( + ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT, + CONF_USERNAME, PRECISION_WHOLE, STATE_OFF, STATE_ON, TEMP_CELSIUS, + TEMP_FAHRENHEIT) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['venstarcolortouch==0.5'] + +_LOGGER = logging.getLogger(__name__) + +ATTR_FAN_STATE = 'fan_state' +ATTR_HVAC_STATE = 'hvac_state' + +DEFAULT_SSL = False + +VALID_FAN_STATES = [STATE_ON, STATE_AUTO] +VALID_THERMOSTAT_MODES = [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_AUTO] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_TIMEOUT, default=5): + vol.All(vol.Coerce(int), vol.Range(min=1)), + vol.Optional(CONF_USERNAME): cv.string, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Venstar thermostat.""" + import venstarcolortouch + + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + host = config.get(CONF_HOST) + timeout = config.get(CONF_TIMEOUT) + + if config.get(CONF_SSL): + proto = 'https' + else: + proto = 'http' + + client = venstarcolortouch.VenstarColorTouch( + addr=host, timeout=timeout, user=username, password=password, + proto=proto) + + add_devices([VenstarThermostat(client)], True) + + +class VenstarThermostat(ClimateDevice): + """Representation of a Venstar thermostat.""" + + def __init__(self, client): + """Initialize the thermostat.""" + self._client = client + + def update(self): + """Update the data from the thermostat.""" + info_success = self._client.update_info() + sensor_success = self._client.update_sensors() + if not info_success or not sensor_success: + _LOGGER.error("Failed to update data") + + @property + def supported_features(self): + """Return the list of supported features.""" + features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | + SUPPORT_OPERATION_MODE) + + if self._client.mode == self._client.MODE_AUTO: + features |= (SUPPORT_TARGET_TEMPERATURE_HIGH | + SUPPORT_TARGET_TEMPERATURE_LOW) + + if self._client.hum_active == 1: + features |= SUPPORT_TARGET_HUMIDITY + + return features + + @property + def name(self): + """Return the name of the thermostat.""" + return self._client.name + + @property + def precision(self): + """Return the precision of the system. + + Venstar temperature values are passed back and forth in the + API as whole degrees C or F. + """ + return PRECISION_WHOLE + + @property + def temperature_unit(self): + """Return the unit of measurement, as defined by the API.""" + if self._client.tempunits == self._client.TEMPUNITS_F: + return TEMP_FAHRENHEIT + else: + return TEMP_CELSIUS + + @property + def fan_list(self): + """Return the list of available fan modes.""" + return VALID_FAN_STATES + + @property + def operation_list(self): + """Return the list of available operation modes.""" + return VALID_THERMOSTAT_MODES + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._client.get_indoor_temp() + + @property + def current_humidity(self): + """Return the current humidity.""" + return self._client.get_indoor_humidity() + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + if self._client.mode == self._client.MODE_HEAT: + return STATE_HEAT + elif self._client.mode == self._client.MODE_COOL: + return STATE_COOL + elif self._client.mode == self._client.MODE_AUTO: + return STATE_AUTO + else: + return STATE_OFF + + @property + def current_fan_mode(self): + """Return the fan setting.""" + if self._client.fan == self._client.FAN_AUTO: + return STATE_AUTO + else: + return STATE_ON + + @property + def device_state_attributes(self): + """Return the optional state attributes.""" + return { + ATTR_FAN_STATE: self._client.fanstate, + ATTR_HVAC_STATE: self._client.state, + } + + @property + def target_temperature(self): + """Return the target temperature we try to reach.""" + if self._client.mode == self._client.MODE_HEAT: + return self._client.heattemp + elif self._client.mode == self._client.MODE_COOL: + return self._client.cooltemp + else: + return None + + @property + def target_temperature_low(self): + """Return the lower bound temp if auto mode is on.""" + if self._client.mode == self._client.MODE_AUTO: + return self._client.heattemp + else: + return None + + @property + def target_temperature_high(self): + """Return the upper bound temp if auto mode is on.""" + if self._client.mode == self._client.MODE_AUTO: + return self._client.cooltemp + else: + return None + + @property + def target_humidity(self): + """Return the humidity we try to reach.""" + return self._client.hum_setpoint + + @property + def min_humidity(self): + """Return the minimum humidity. Hardcoded to 0 in API.""" + return 0 + + @property + def max_humidity(self): + """Return the maximum humidity. Hardcoded to 60 in API.""" + return 60 + + def _set_operation_mode(self, operation_mode): + """Change the operation mode (internal).""" + if operation_mode == STATE_HEAT: + success = self._client.set_mode(self._client.MODE_HEAT) + elif operation_mode == STATE_COOL: + success = self._client.set_mode(self._client.MODE_COOL) + elif operation_mode == STATE_AUTO: + success = self._client.set_mode(self._client.MODE_AUTO) + else: + success = self._client.set_mode(self._client.MODE_OFF) + + if not success: + _LOGGER.error("Failed to change the operation mode") + return success + + def set_temperature(self, **kwargs): + """Set a new target temperature.""" + set_temp = True + operation_mode = kwargs.get(ATTR_OPERATION_MODE, self._client.mode) + temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) + temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) + temperature = kwargs.get(ATTR_TEMPERATURE) + + if operation_mode != self._client.mode: + set_temp = self._set_operation_mode(operation_mode) + + if set_temp: + if operation_mode == self._client.MODE_HEAT: + success = self._client.set_setpoints( + temperature, self._client.cooltemp) + elif operation_mode == self._client.MODE_COOL: + success = self._client.set_setpoints( + self._client.heattemp, temperature) + elif operation_mode == self._client.MODE_AUTO: + success = self._client.set_setpoints(temp_low, temp_high) + else: + _LOGGER.error("The thermostat is currently not in a mode " + "that supports target temperature") + + if not success: + _LOGGER.error("Failed to change the temperature") + + def set_fan_mode(self, fan): + """Set new target fan mode.""" + if fan == STATE_ON: + success = self._client.set_fan(self._client.FAN_ON) + else: + success = self._client.set_fan(self._client.FAN_AUTO) + + if not success: + _LOGGER.error("Failed to change the fan mode") + + def set_operation_mode(self, operation_mode): + """Set new target operation mode.""" + self._set_operation_mode(operation_mode) + + def set_humidity(self, humidity): + """Set new target humidity.""" + success = self._client.set_hum_setpoint(humidity) + + if not success: + _LOGGER.error("Failed to change the target humidity level") diff --git a/requirements_all.txt b/requirements_all.txt index c708548365a..fba7605d688 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1174,6 +1174,9 @@ user-agents==1.1.0 # homeassistant.components.camera.uvc uvcclient==0.10.1 +# homeassistant.components.climate.venstar +venstarcolortouch==0.5 + # homeassistant.components.volvooncall volvooncall==0.4.0