Add Honeywell US thermostat support

This adds support for the US variant of the Honeywell connected
thermostat. The interface is super simple, so this doesn't add
any external dependencies. It supports basic temperature, setpoint,
and control.

Issue #998 notes that the existing honeywell module doesn't work
for US models, which is because they are totally different. In order
to indicate to the honeywell platform module that the thermostat
is a US-type, we key off of whether or not the thermostat id is
provided. This is something that US people have (and require to
identify one of potentially multiple thermostats in their account)
and EU people will not.
This commit is contained in:
Dan Smith 2016-02-06 04:09:46 +00:00
parent 2ba237eac8
commit 062fe79b3f

View file

@ -9,33 +9,32 @@ https://home-assistant.io/components/thermostat.honeywell/
import logging
import socket
import requests
from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS,
TEMP_FAHRENHEIT)
REQUIREMENTS = ['evohomeclient==0.2.4']
_LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = "away_temperature"
US_SYSTEM_SWITCH_POSITIONS = {1: 'Heat',
2: 'Off',
3: 'Cool'}
US_BASEURL = 'https://mytotalconnectcomfort.com/portal'
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the honeywel thermostat. """
def _setup_round(username, password, config, add_devices):
from evohomeclient import EvohomeClient
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
try:
away_temp = float(config.get(CONF_AWAY_TEMP, 16))
except ValueError:
_LOGGER.error("value entered for item %s should convert to a number",
CONF_AWAY_TEMP)
return False
if username is None or password is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return False
evo_api = EvohomeClient(username, password)
@ -53,6 +52,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the honeywel thermostat. """
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
thermostat_id = config.get('id')
if username is None or password is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return False
if thermostat_id:
add_devices([HoneywellUSThermostat(thermostat_id, username, password)])
else:
return _setup_round(username, password, config, add_devices)
class RoundThermostat(ThermostatDevice):
""" Represents a Honeywell Round Connected thermostat. """
@ -135,3 +152,117 @@ class RoundThermostat(ThermostatDevice):
else:
self._name = data['name']
self._is_dhw = False
class HoneywellUSThermostat(ThermostatDevice):
""" Represents a Honeywell US Thermostat. """
def __init__(self, ident, username, password):
self._ident = ident
self._username = username
self._password = password
self._session = requests.Session()
# Maybe this should be configurable?
self._timeout = 30
# Yeah, really.
self._session.headers['X-Requested-With'] = 'XMLHttpRequest'
self._update()
def _login(self):
self._session.get(US_BASEURL, timeout=self._timeout)
params = {'UserName': self._username,
'Password': self._password,
'RememberMe': 'false',
'timeOffset': 480}
resp = self._session.post(US_BASEURL, params=params,
timeout=self._timeout)
if resp.status_code != 200:
_LOGGER('Login failed for user %(user)s',
dict(user=self._username))
return False
else:
return True
def _get_data(self):
if not self._login():
return
url = '%s/Device/CheckDataSession/%s' % (US_BASEURL, self._ident)
resp = self._session.get(url, timeout=self._timeout)
if resp.status_code < 300:
return resp.json()
else:
return {'error': resp.status_code}
def _set_data(self, data):
if not self._login():
return
url = '%s/Device/SubmitControlScreenChanges' % US_BASEURL
data['DeviceID'] = self._ident
resp = self._session.post(url, data=data, timeout=self._timeout)
if resp.status_code < 300:
return resp.json()
else:
return {'error': resp.status_code}
def _update(self):
self._data = self._get_data()['latestData']
@property
def is_fan_on(self):
return self._data['fanData']['fanIsRunning']
@property
def name(self):
return 'honeywell'
@property
def unit_of_measurement(self):
unit = self._data['uiData']['DisplayUnits']
if unit == 'F':
return TEMP_FAHRENHEIT
else:
return TEMP_CELCIUS
@property
def current_temperature(self):
self._update()
return self._data['uiData']['DispTemperature']
@property
def target_temperature(self):
setpoint = US_SYSTEM_SWITCH_POSITIONS.get(
self._data['uiData']['SystemSwitchPosition'],
'Off')
return self._data['uiData']['%sSetpoint' % setpoint]
def set_temperature(self, temperature):
""" Set target temperature. """
data = {'SystemSwitch': None,
'HeatSetpoint': None,
'CoolSetpoint': None,
'HeatNextPeriod': None,
'CoolNextPeriod': None,
'StatusHeat': None,
'StatusCool': None,
'FanMode': None}
setpoint = US_SYSTEM_SWITCH_POSITIONS.get(
self._data['uiData']['SystemSwitchPosition'],
'Off')
data['%sSetpoint' % setpoint] = temperature
self._set_data(data)
@property
def device_state_attributes(self):
""" Return device specific state attributes. """
fanmodes = {0: "auto",
1: "on",
2: "circulate"}
return {"fan": (self._data['fanData']['fanIsRunning'] and
'running' or 'idle'),
"fanmode": fanmodes[self._data['fanData']['fanMode']]}
def turn_away_mode_on(self):
pass
def turn_away_mode_off(self):
pass