|
47 | 47 |
|
48 | 48 | import datetime
|
49 | 49 | import httplib
|
| 50 | +import urllib |
50 | 51 | import json
|
51 | 52 | import logging
|
52 | 53 |
|
@@ -86,13 +87,63 @@ def _getConn(self):
|
86 | 87 | """Used internally to get a connection to the tstat."""
|
87 | 88 | return httplib.HTTPConnection(self.address)
|
88 | 89 |
|
89 |
| - def _post(self, location, params): |
| 90 | + def _post(self, key, value): |
90 | 91 | """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 |
96 | 147 |
|
97 | 148 | def _get(self, key, raw=False):
|
98 | 149 | """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):
|
136 | 187 | l.debug("Using cached entry")
|
137 | 188 | response = self.cache[newest[0]].data[newest[1]]
|
138 | 189 | 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 |
147 | 213 | self.cache[getter[0]] = CacheEntry(getter[0], response)
|
148 | 214 |
|
149 | 215 | # Allow mappings to subdictionaries in json data
|
@@ -177,26 +243,54 @@ def getTstatMode(self, raw=False):
|
177 | 243 | """Returns current thermostat mode."""
|
178 | 244 | return self._get('tmode', raw)
|
179 | 245 |
|
| 246 | + def setTstatMode(self, value): |
| 247 | + """Sets thermostat mode.""" |
| 248 | + return self._post('tmode', value) |
| 249 | + |
180 | 250 | def getFanMode(self, raw=False):
|
181 | 251 | """Returns current fan mode."""
|
182 | 252 | return self._get('fmode', raw)
|
183 | 253 |
|
| 254 | + def setFanMode(self, value): |
| 255 | + """Sets fan mode.""" |
| 256 | + return self._post('fmode', value) |
| 257 | + |
184 | 258 | def getOverride(self, raw=False):
|
185 |
| - """Returns current override setting?""" |
| 259 | + """Returns current override setting""" |
186 | 260 | return self._get('override', raw)
|
187 | 261 |
|
| 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 | + |
188 | 270 | def getHoldState(self, raw=False):
|
189 | 271 | """Returns current hold state."""
|
190 | 272 | return self._get('hold', raw)
|
191 | 273 |
|
| 274 | + def setHoldState(self, value): |
| 275 | + """Sets hold state.""" |
| 276 | + return self._post('hold', value) |
| 277 | + |
192 | 278 | def getHeatPoint(self, raw=False):
|
193 | 279 | """Returns current set point for heat."""
|
194 | 280 | return self._get('t_heat', raw)
|
195 | 281 |
|
| 282 | + def setHeatPoint(self, value): |
| 283 | + """Sets point for heat.""" |
| 284 | + return self._post('t_heat', value) |
| 285 | + |
196 | 286 | def getCoolPoint(self, raw=False):
|
197 | 287 | """Returns current set point for cooling."""
|
198 | 288 | return self._get('t_cool', raw)
|
199 | 289 |
|
| 290 | + def setCoolPoint(self, value): |
| 291 | + """Sets point for cooling.""" |
| 292 | + return self._post('t_cool', value) |
| 293 | + |
200 | 294 | def getSetPoints(self, raw=False):
|
201 | 295 | """Returns both heating and cooling set points."""
|
202 | 296 | return (self.getHeatPoint(), self.getCoolPoint())
|
@@ -236,22 +330,19 @@ def getCoolUsageYesterday(self, raw=False):
|
236 | 330 |
|
237 | 331 | def isOK(self):
|
238 | 332 | """Returns true if thermostat reports that it is OK."""
|
239 |
| - pass |
| 333 | + return self._get('errstatus') == 'OK' |
240 | 334 |
|
241 | 335 | def getErrStatus(self):
|
242 | 336 | """Returns current error code or 0 if everything is OK."""
|
243 |
| - pass |
| 337 | + return self._get('errstatus') |
244 | 338 |
|
245 | 339 | def getEventLog(self):
|
246 | 340 | """Returns events?"""
|
247 | 341 | pass
|
248 | 342 |
|
249 |
| - def setCloudMode(self, state=True): |
| 343 | + def setCloudMode(self, value): |
250 | 344 | """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) |
255 | 346 |
|
256 | 347 | def main():
|
257 | 348 | import sys
|
|
0 commit comments