Skip to content

Commit f184eec

Browse files
author
Paul Jennings
committed
Lots of updates including error checking and ability to set values.
1 parent 82e57fe commit f184eec

File tree

1 file changed

+113
-22
lines changed

1 file changed

+113
-22
lines changed

TStat.py

Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
import datetime
4949
import httplib
50+
import urllib
5051
import json
5152
import logging
5253

@@ -86,13 +87,63 @@ def _getConn(self):
8687
"""Used internally to get a connection to the tstat."""
8788
return httplib.HTTPConnection(self.address)
8889

89-
def _post(self, location, params):
90+
def _post(self, key, value):
9091
"""Used internally to modify tstat settings (e.g. cloud mode)."""
91-
params = urllib.urlencode(params)
92-
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
93-
conn = self._getConn()
94-
conn.request("POST", location, params, headers)
95-
return conn.getresponse()
92+
93+
l = self.logger
94+
95+
# Check for valid request
96+
if not self.api.has_key(key):
97+
l.error("%s does not exist in API" % key)
98+
return False
99+
100+
# Retrieve the mapping from api key to thermostat URL
101+
entry = self.api[key]
102+
l.debug("Got API entry: %s" % entry)
103+
104+
try:
105+
if len(entry.setters) < 1:
106+
raise TypeError
107+
except TypeError:
108+
l.error("%s cannot be set (maybe readonly?)" % key)
109+
return False
110+
111+
# Check for valid values
112+
if entry.valueMap is not None:
113+
inverse = dict((v,k) for k, v in entry.valueMap.iteritems())
114+
if not inverse.has_key(value) and not entry.valueMap.has_key(value):
115+
l.warning("Value '%s' may not be a valid value for '%s'" % (value, key))
116+
elif inverse.has_key(value):
117+
value = inverse[value]
118+
119+
for setter in entry.setters:
120+
location = setter[0]
121+
jsonKey = setter[1]
122+
if entry.usesJson:
123+
params = json.dumps({jsonKey: value})
124+
else:
125+
params = urllib.urlencode({jsonKey: value})
126+
127+
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
128+
conn = self._getConn()
129+
conn.request("POST", location, params, headers)
130+
response = conn.getresponse()
131+
if response.status != 200:
132+
l.error("Error %s while trying to set '%s' with '%s'" % (response.status, location, params))
133+
continue
134+
data = response.read()
135+
response.close()
136+
137+
success = False
138+
for s in self.api.successStrings:
139+
if data.startswith(s):
140+
success = True
141+
break
142+
143+
if not success:
144+
l.error("Error trying to set '%s' with '%s': %s" % (location, params, data))
145+
l.debug("Response: %s" % data)
146+
return True
96147

97148
def _get(self, key, raw=False):
98149
"""Used internally to retrieve data from the tstat and process it with JSON if necessary."""
@@ -136,14 +187,29 @@ def _get(self, key, raw=False):
136187
l.debug("Using cached entry")
137188
response = self.cache[newest[0]].data[newest[1]]
138189
else:
139-
# Either data was not cached or cache was expired
140-
getter = entry.getters[0]
141-
# TODO: Change back to access actual tstat
142-
conn = self._getConn()
143-
conn.request("GET", getter[0])
144-
#TODO: Error checking
145-
response = json.loads(conn.getresponse().read())
146-
l.debug("Got response: %s" % response)
190+
for getter in entry.getters:
191+
# Either data was not cached or cache was expired
192+
conn = self._getConn()
193+
conn.request("GET", getter[0])
194+
response = conn.getresponse()
195+
if response.status != 200:
196+
l.warning("Request for '%s' failed (error %s)" % (getter[0], response.status))
197+
response = None
198+
continue
199+
data = response.read()
200+
response.close()
201+
l.debug("Got response: %s" % data)
202+
try:
203+
response = json.loads(data)
204+
break
205+
except:
206+
l.warning("Some problem with response: %s" % data)
207+
response = None
208+
continue
209+
210+
if response is None:
211+
l.error("Unable to retrieve '%s' from any of %s" % (key, entry.getters))
212+
return
147213
self.cache[getter[0]] = CacheEntry(getter[0], response)
148214

149215
# Allow mappings to subdictionaries in json data
@@ -177,26 +243,54 @@ def getTstatMode(self, raw=False):
177243
"""Returns current thermostat mode."""
178244
return self._get('tmode', raw)
179245

246+
def setTstatMode(self, value):
247+
"""Sets thermostat mode."""
248+
return self._post('tmode', value)
249+
180250
def getFanMode(self, raw=False):
181251
"""Returns current fan mode."""
182252
return self._get('fmode', raw)
183253

254+
def setFanMode(self, value):
255+
"""Sets fan mode."""
256+
return self._post('fmode', value)
257+
184258
def getOverride(self, raw=False):
185-
"""Returns current override setting?"""
259+
"""Returns current override setting"""
186260
return self._get('override', raw)
187261

262+
def getPower(self, raw=False):
263+
"""Returns power?"""
264+
return self._get('power', raw)
265+
266+
def setPower(self, value):
267+
"""Sets power?"""
268+
return self._post('power', value)
269+
188270
def getHoldState(self, raw=False):
189271
"""Returns current hold state."""
190272
return self._get('hold', raw)
191273

274+
def setHoldState(self, value):
275+
"""Sets hold state."""
276+
return self._post('hold', value)
277+
192278
def getHeatPoint(self, raw=False):
193279
"""Returns current set point for heat."""
194280
return self._get('t_heat', raw)
195281

282+
def setHeatPoint(self, value):
283+
"""Sets point for heat."""
284+
return self._post('t_heat', value)
285+
196286
def getCoolPoint(self, raw=False):
197287
"""Returns current set point for cooling."""
198288
return self._get('t_cool', raw)
199289

290+
def setCoolPoint(self, value):
291+
"""Sets point for cooling."""
292+
return self._post('t_cool', value)
293+
200294
def getSetPoints(self, raw=False):
201295
"""Returns both heating and cooling set points."""
202296
return (self.getHeatPoint(), self.getCoolPoint())
@@ -236,22 +330,19 @@ def getCoolUsageYesterday(self, raw=False):
236330

237331
def isOK(self):
238332
"""Returns true if thermostat reports that it is OK."""
239-
pass
333+
return self._get('errstatus') == 'OK'
240334

241335
def getErrStatus(self):
242336
"""Returns current error code or 0 if everything is OK."""
243-
pass
337+
return self._get('errstatus')
244338

245339
def getEventLog(self):
246340
"""Returns events?"""
247341
pass
248342

249-
def setCloudMode(self, state=True):
343+
def setCloudMode(self, value):
250344
"""Sets cloud mode to state."""
251-
command = "on"
252-
if not state:
253-
command = "off"
254-
return self._post("/cloud/mode", {'command': command})
345+
return self._post("cloud_mode", value)
255346

256347
def main():
257348
import sys

0 commit comments

Comments
 (0)