diff --git a/nest.py b/nest.py index 7fcf863..fc02eb3 100755 --- a/nest.py +++ b/nest.py @@ -21,125 +21,7 @@ import urllib2 import sys from optparse import OptionParser - -try: - import json -except ImportError: - try: - import simplejson as json - except ImportError: - print "No json library available. I recommend installing either python-json" - print "or simpejson." - sys.exit(-1) - -class Nest: - def __init__(self, username, password, serial=None, index=0, units="F"): - self.username = username - self.password = password - self.serial = serial - self.units = units - self.index = index - - def loads(self, res): - if hasattr(json, "loads"): - res = json.loads(res) - else: - res = json.read(res) - return res - - def login(self): - data = urllib.urlencode({"username": self.username, "password": self.password}) - - req = urllib2.Request("https://home.nest.com/user/login", - data, - {"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4"}) - - res = urllib2.urlopen(req).read() - - res = self.loads(res) - - self.transport_url = res["urls"]["transport_url"] - self.access_token = res["access_token"] - self.userid = res["userid"] - - def get_status(self): - req = urllib2.Request(self.transport_url + "/v2/mobile/user." + self.userid, - headers={"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4", - "Authorization":"Basic " + self.access_token, - "X-nl-user-id": self.userid, - "X-nl-protocol-version": "1"}) - - res = urllib2.urlopen(req).read() - - res = self.loads(res) - - self.structure_id = res["structure"].keys()[0] - - if (self.serial is None): - self.device_id = res["structure"][self.structure_id]["devices"][self.index] - self.serial = self.device_id.split(".")[1] - - self.status = res - - #print "res.keys", res.keys() - #print "res[structure][structure_id].keys", res["structure"][self.structure_id].keys() - #print "res[device].keys", res["device"].keys() - #print "res[device][serial].keys", res["device"][self.serial].keys() - #print "res[shared][serial].keys", res["shared"][self.serial].keys() - - def temp_in(self, temp): - if (self.units == "F"): - return (temp - 32.0) / 1.8 - else: - return temp - - def temp_out(self, temp): - if (self.units == "F"): - return temp*1.8 + 32.0 - else: - return temp - - def show_status(self): - shared = self.status["shared"][self.serial] - device = self.status["device"][self.serial] - - allvars = shared - allvars.update(device) - - for k in sorted(allvars.keys()): - print k + "."*(32-len(k)) + ":", allvars[k] - - def show_curtemp(self): - temp = self.status["shared"][self.serial]["current_temperature"] - temp = self.temp_out(temp) - - print "%0.1f" % temp - - def set_temperature(self, temp): - temp = self.temp_in(temp) - - data = '{"target_change_pending":true,"target_temperature":' + '%0.1f' % temp + '}' - req = urllib2.Request(self.transport_url + "/v2/put/shared." + self.serial, - data, - {"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4", - "Authorization":"Basic " + self.access_token, - "X-nl-protocol-version": "1"}) - - res = urllib2.urlopen(req).read() - - print res - - def set_fan(self, state): - data = '{"fan_mode":"' + str(state) + '"}' - req = urllib2.Request(self.transport_url + "/v2/put/device." + self.serial, - data, - {"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4", - "Authorization":"Basic " + self.access_token, - "X-nl-protocol-version": "1"}) - - res = urllib2.urlopen(req).read() - - print res +from pynest.pynest import Nest def create_parser(): parser = OptionParser(usage="nest [options] command [command_options] [command_args]", diff --git a/pynest/__init__.py b/pynest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pynest/pynest.py b/pynest/pynest.py new file mode 100644 index 0000000..898f759 --- /dev/null +++ b/pynest/pynest.py @@ -0,0 +1,132 @@ +#! /usr/bin/python + +# nest.py -- a python interface to the Nest Thermostat +# by Scott M Baker, smbaker@gmail.com, http://www.smbaker.com/ +# updated by Bob Pasker bob@pasker.net http://pasker.net +# +# Licensing: +# This is distributed unider the Creative Commons 3.0 Non-commecrial, +# Attribution, Share-Alike license. You can use the code for noncommercial +# purposes. You may NOT sell it. If you do use it, then you must make an +# attribution to me (i.e. Include my name and thank me for the hours I spent +# on this) +# +# Acknowledgements: +# Chris Burris's Siri Nest Proxy was very helpful to learn the nest's +# authentication and some bits of the protocol. + +import urllib +import urllib2 +import requests + +try: + import json +except ImportError: + import simplejson as json + +class Nest: + def __init__(self, username, password, serial=None, index=0, units="F", debug=False): + self.username = username + self.password = password + self.serial = serial + self.units = units + self.index = index + self.debug = debug + + def login(self): + + response = requests.post("https://home.nest.com/user/login", + data = {"username":self.username, "password" : self.password}, + headers = {"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4"}) + + response.raise_for_status() + + res = response.json() + self.transport_url = res["urls"]["transport_url"] + self.access_token = res["access_token"] + self.userid = res["userid"] + # print self.transport_url, self.access_token, self.userid + + def get_status(self): + response = requests.get(self.transport_url + "/v2/mobile/user." + self.userid, + headers={"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4", + "Authorization":"Basic " + self.access_token, + "X-nl-user-id": self.userid, + "X-nl-protocol-version": "1"}) + + response.raise_for_status() + res = response.json() + + self.structure_id = res["structure"].keys()[0] + + if (self.serial is None): + self.device_id = res["structure"][self.structure_id]["devices"][self.index] + self.serial = self.device_id.split(".")[1] + + self.status = res + + #print "res.keys", res.keys() + #print "res[structure][structure_id].keys", res["structure"][self.structure_id].keys() + #print "res[device].keys", res["device"].keys() + #print "res[device][serial].keys", res["device"][self.serial].keys() + #print "res[shared][serial].keys", res["shared"][self.serial].keys() + + def temp_in(self, temp): + if (self.units == "F"): + return (temp - 32.0) / 1.8 + else: + return temp + + def temp_out(self, temp): + if (self.units == "F"): + return temp*1.8 + 32.0 + else: + return temp + + def show_status(self): + shared = self.status["shared"][self.serial] + device = self.status["device"][self.serial] + + allvars = shared + allvars.update(device) + + for k in sorted(allvars.keys()): + print k + "."*(32-len(k)) + ":", allvars[k] + + def show_curtemp(self): + temp = self.status["shared"][self.serial]["current_temperature"] + temp = self.temp_out(temp) + + print "%0.1f" % temp + + def _set(self, data, which): + if (self.debug): print json.dumps(data) + url = "%s/v2/put/%s.%s" % (self.transport_url, which, self.serial) + if (self.debug): print url + response = requests.post(url, + data = json.dumps(data), + headers = {"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4", + "Authorization":"Basic " + self.access_token, + "X-nl-protocol-version": "1"}) + + if response.status_code > 200: + if (self.debug): print response.content + response.raise_for_status() + return response + + def _set_shared(self, data): + self._set(data, "shared") + + def _set_device(self, data): + self._set(data, "device") + + def set_temperature(self, temp): + return self._set_shared({ + "target_change_pending": True, + "target_temperature" : self.temp_in(temp) + }) + + def set_fan(self, state): + return self._set_device({ + "fan_mode": str(state) + }) diff --git a/setup.py b/setup.py index dbd9861..35724ea 100755 --- a/setup.py +++ b/setup.py @@ -9,4 +9,6 @@ author_email='smbaker@gmail.com', url='http://www.smbaker.com/', scripts=['nest.py'], + packages=['pynest'], + install_requires = ['requests'] )