248 lines
8.2 KiB
Python
248 lines
8.2 KiB
Python
"""
|
|
Support for Radio Thermostat wifi-enabled home thermostats.
|
|
|
|
For more details about this platform, please refer to the documentation at
|
|
https://home-assistant.io/components/climate.radiotherm/
|
|
"""
|
|
import datetime
|
|
import logging
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.climate import (
|
|
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF,
|
|
ClimateDevice, PLATFORM_SCHEMA)
|
|
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
REQUIREMENTS = ['radiotherm==1.3']
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
ATTR_FAN = 'fan'
|
|
ATTR_MODE = 'mode'
|
|
|
|
CONF_HOLD_TEMP = 'hold_temp'
|
|
CONF_AWAY_TEMPERATURE_HEAT = 'away_temperature_heat'
|
|
CONF_AWAY_TEMPERATURE_COOL = 'away_temperature_cool'
|
|
|
|
DEFAULT_AWAY_TEMPERATURE_HEAT = 60
|
|
DEFAULT_AWAY_TEMPERATURE_COOL = 85
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
|
|
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
|
|
vol.Optional(CONF_AWAY_TEMPERATURE_HEAT,
|
|
default=DEFAULT_AWAY_TEMPERATURE_HEAT): vol.Coerce(float),
|
|
vol.Optional(CONF_AWAY_TEMPERATURE_COOL,
|
|
default=DEFAULT_AWAY_TEMPERATURE_COOL): vol.Coerce(float),
|
|
})
|
|
|
|
|
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
|
"""Set up the Radio Thermostat."""
|
|
import radiotherm
|
|
|
|
hosts = []
|
|
if CONF_HOST in config:
|
|
hosts = config[CONF_HOST]
|
|
else:
|
|
hosts.append(radiotherm.discover.discover_address())
|
|
|
|
if hosts is None:
|
|
_LOGGER.error("No Radiotherm Thermostats detected")
|
|
return False
|
|
|
|
hold_temp = config.get(CONF_HOLD_TEMP)
|
|
away_temps = [
|
|
config.get(CONF_AWAY_TEMPERATURE_HEAT),
|
|
config.get(CONF_AWAY_TEMPERATURE_COOL)
|
|
]
|
|
tstats = []
|
|
|
|
for host in hosts:
|
|
try:
|
|
tstat = radiotherm.get_thermostat(host)
|
|
tstats.append(RadioThermostat(tstat, hold_temp, away_temps))
|
|
except OSError:
|
|
_LOGGER.exception("Unable to connect to Radio Thermostat: %s",
|
|
host)
|
|
|
|
add_devices(tstats, True)
|
|
|
|
|
|
class RadioThermostat(ClimateDevice):
|
|
"""Representation of a Radio Thermostat."""
|
|
|
|
def __init__(self, device, hold_temp, away_temps):
|
|
"""Initialize the thermostat."""
|
|
self.device = device
|
|
self.set_time()
|
|
self._target_temperature = None
|
|
self._current_temperature = None
|
|
self._current_operation = STATE_IDLE
|
|
self._name = None
|
|
self._fmode = None
|
|
self._tmode = None
|
|
self._tstate = None
|
|
self._hold_temp = hold_temp
|
|
self._away = False
|
|
self._away_temps = away_temps
|
|
self._prev_temp = None
|
|
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of the Radio Thermostat."""
|
|
return self._name
|
|
|
|
@property
|
|
def temperature_unit(self):
|
|
"""Return the unit of measurement."""
|
|
return TEMP_FAHRENHEIT
|
|
|
|
@property
|
|
def device_state_attributes(self):
|
|
"""Return the device specific state attributes."""
|
|
return {
|
|
ATTR_FAN: self._fmode,
|
|
ATTR_MODE: self._tmode,
|
|
}
|
|
|
|
@property
|
|
def current_temperature(self):
|
|
"""Return the current temperature."""
|
|
return self._current_temperature
|
|
|
|
@property
|
|
def current_operation(self):
|
|
"""Return the current operation. head, cool idle."""
|
|
return self._current_operation
|
|
|
|
@property
|
|
def operation_list(self):
|
|
"""Return the operation modes list."""
|
|
return self._operation_list
|
|
|
|
@property
|
|
def target_temperature(self):
|
|
"""Return the temperature we try to reach."""
|
|
return self._target_temperature
|
|
|
|
@property
|
|
def is_away_mode_on(self):
|
|
"""Return true if away mode is on."""
|
|
return self._away
|
|
|
|
def update(self):
|
|
"""Update and validate the data from the thermostat."""
|
|
current_temp = self.device.temp['raw']
|
|
if current_temp == -1:
|
|
_LOGGER.error("Couldn't get valid temperature reading")
|
|
return
|
|
self._current_temperature = current_temp
|
|
self._name = self.device.name['raw']
|
|
try:
|
|
self._fmode = self.device.fmode['human']
|
|
except AttributeError:
|
|
_LOGGER.error("Couldn't get valid fan mode reading")
|
|
try:
|
|
self._tmode = self.device.tmode['human']
|
|
except AttributeError:
|
|
_LOGGER.error("Couldn't get valid thermostat mode reading")
|
|
try:
|
|
self._tstate = self.device.tstate['human']
|
|
except AttributeError:
|
|
_LOGGER.error("Couldn't get valid thermostat state reading")
|
|
|
|
if self._tmode == 'Cool':
|
|
target_temp = self.device.t_cool['raw']
|
|
if target_temp == -1:
|
|
_LOGGER.error("Couldn't get target reading")
|
|
return
|
|
self._target_temperature = target_temp
|
|
self._current_operation = STATE_COOL
|
|
elif self._tmode == 'Heat':
|
|
target_temp = self.device.t_heat['raw']
|
|
if target_temp == -1:
|
|
_LOGGER.error("Couldn't get valid target reading")
|
|
return
|
|
self._target_temperature = target_temp
|
|
self._current_operation = STATE_HEAT
|
|
elif self._tmode == 'Auto':
|
|
if self._tstate == 'Cool':
|
|
target_temp = self.device.t_cool['raw']
|
|
if target_temp == -1:
|
|
_LOGGER.error("Couldn't get valid target reading")
|
|
return
|
|
self._target_temperature = target_temp
|
|
elif self._tstate == 'Heat':
|
|
target_temp = self.device.t_heat['raw']
|
|
if target_temp == -1:
|
|
_LOGGER.error("Couldn't get valid target reading")
|
|
return
|
|
self._target_temperature = target_temp
|
|
self._current_operation = STATE_AUTO
|
|
else:
|
|
self._current_operation = STATE_IDLE
|
|
|
|
def set_temperature(self, **kwargs):
|
|
"""Set new target temperature."""
|
|
temperature = kwargs.get(ATTR_TEMPERATURE)
|
|
if temperature is None:
|
|
return
|
|
if self._current_operation == STATE_COOL:
|
|
self.device.t_cool = round(temperature * 2.0) / 2.0
|
|
elif self._current_operation == STATE_HEAT:
|
|
self.device.t_heat = round(temperature * 2.0) / 2.0
|
|
elif self._current_operation == STATE_AUTO:
|
|
if self._tstate == 'Cool':
|
|
self.device.t_cool = round(temperature * 2.0) / 2.0
|
|
elif self._tstate == 'Heat':
|
|
self.device.t_heat = round(temperature * 2.0) / 2.0
|
|
|
|
if self._hold_temp or self._away:
|
|
self.device.hold = 1
|
|
else:
|
|
self.device.hold = 0
|
|
|
|
def set_time(self):
|
|
"""Set device time."""
|
|
now = datetime.datetime.now()
|
|
self.device.time = {
|
|
'day': now.weekday(),
|
|
'hour': now.hour,
|
|
'minute': now.minute
|
|
}
|
|
|
|
def set_operation_mode(self, operation_mode):
|
|
"""Set operation mode (auto, cool, heat, off)."""
|
|
if operation_mode == STATE_OFF:
|
|
self.device.tmode = 0
|
|
elif operation_mode == STATE_AUTO:
|
|
self.device.tmode = 3
|
|
elif operation_mode == STATE_COOL:
|
|
self.device.t_cool = round(self._target_temperature * 2.0) / 2.0
|
|
elif operation_mode == STATE_HEAT:
|
|
self.device.t_heat = round(self._target_temperature * 2.0) / 2.0
|
|
|
|
def turn_away_mode_on(self):
|
|
"""Turn away on.
|
|
|
|
The RTCOA app simulates away mode by using a hold.
|
|
"""
|
|
away_temp = None
|
|
if not self._away:
|
|
self._prev_temp = self._target_temperature
|
|
if self._current_operation == STATE_HEAT:
|
|
away_temp = self._away_temps[0]
|
|
elif self._current_operation == STATE_COOL:
|
|
away_temp = self._away_temps[1]
|
|
self._away = True
|
|
self.set_temperature(temperature=away_temp)
|
|
|
|
def turn_away_mode_off(self):
|
|
"""Turn away off."""
|
|
self._away = False
|
|
self.set_temperature(temperature=self._prev_temp)
|