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:
parent
2ba237eac8
commit
062fe79b3f
1 changed files with 141 additions and 10 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue