From 5667439169447248d80be69c68af7047c253cd90 Mon Sep 17 00:00:00 2001 From: smbaker Date: Fri, 11 May 2012 05:32:59 -0700 Subject: [PATCH 1/3] initial checkin --- README.md | 19 ++++- nest.py | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 3 deletions(-) create mode 100755 nest.py diff --git a/README.md b/README.md index a1d5e7b..4328f90 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ -pynest -====== + pynest -- a python interface to the Nest Thermostat + by Scott M Baker, smbaker@gmail.com, http://www.smbaker.com/ + + Usage: + 'nest.py help' will tell you what to do and how to do it + + 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. -python API for talking to nest thermostat \ No newline at end of file diff --git a/nest.py b/nest.py new file mode 100755 index 0000000..3b2f2a8 --- /dev/null +++ b/nest.py @@ -0,0 +1,217 @@ +#! /usr/bin/python + +# nest.py -- a python interface to the Nest Thermostat +# by Scott M Baker, smbaker@gmail.com, http://www.smbaker.com/ +# +# Usage: +# 'nest.py help' will tell you what to do and how to do it +# +# 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 json +import sys +from optparse import OptionParser + +class Nest: + def __init__(self, username, password, serial=None, units="F"): + self.username = username + self.password = password + self.serial = serial + self.units = units + + 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"][0] + 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 + +def create_parser(): + parser = OptionParser(usage="nest [options] command [command_options] [command_args]", + description="Commands: fan temp", + version="unknown") + + parser.add_option("-u", "--user", dest="user", + help="username for nest.com", metavar="USER", default=None) + + parser.add_option("-p", "--password", dest="password", + help="password for nest.com", metavar="PASSWORD", default=None) + + parser.add_option("-c", "--celsius", dest="celsius", action="store_true", default=False, + help="use celsius instead of farenheit") + + return parser + +def help(): + print "syntax: nest [options] command [command_args]" + print "options:" + print " --user ... username on nest.com" + print " --password ... password on nest.com" + print " --celsius ... use celsius (the default is farenheit)" + print + print "commands: temp, fan, show, curtemp, curhumid" + print " temp ... set target temperature" + print " fan [auto|on] ... set fan state" + print " show ... show everything" + print " curtemp ... print current temperature" + print " curhumid ... print current humidity" + print + print "examples:" + print " nest.py --user joe@user.com --password swordfish temp 73" + print " nest.py --user joe@user.com --password swordfish fan auto" + +def main(): + parser = create_parser() + (opts, args) = parser.parse_args() + + if (len(args)==0) or (args[0]=="help"): + help() + sys.exit(-1) + + if (not opts.user) or (not opts.password): + print "how about specifying a --user and --password option next time?" + sys.exit(-1) + + if opts.celsius: + units = "C" + else: + units = "F" + + n = Nest(opts.user, opts.password, units=units) + n.login() + n.get_status() + + cmd = args[0] + + if (cmd == "temp"): + if len(args)<2: + print "please specify a temperature" + sys.exit(-1) + n.set_temperature(int(args[1])) + elif (cmd == "fan"): + if len(args)<2: + print "please specify a fan state of 'on' or 'auto'" + sys.exit(-1) + n.set_fan(args[1]) + elif (cmd == "show"): + n.show_status() + elif (cmd == "curtemp"): + n.show_curtemp() + elif (cmd == "curhumid"): + print n.status["device"][n.serial]["current_humidity"] + else: + print "misunderstood command:", cmd + print "do 'nest.py help' for help" + +if __name__=="__main__": + main() + + + + + From 5eefa54b069902e1b3e38df1fe87f83a47dea5dd Mon Sep 17 00:00:00 2001 From: root Date: Fri, 11 May 2012 05:41:49 -0700 Subject: [PATCH 2/3] add python setup script --- setup.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 setup.py diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..dbd9861 --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +from distutils.core import setup + +setup(name='pynest', + version='1.0', + description='Python API for Nest Thermostat', + author='Scott Baker', + author_email='smbaker@gmail.com', + url='http://www.smbaker.com/', + scripts=['nest.py'], + ) From 99aa6bb2ea4df050556319c87a0933923e4e6617 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 11 May 2012 05:42:04 -0700 Subject: [PATCH 3/3] more useful information in the readme --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4328f90..d2f934f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,20 @@ - pynest -- a python interface to the Nest Thermostat + pynest -- a python interface for the Nest Thermostat by Scott M Baker, smbaker@gmail.com, http://www.smbaker.com/ Usage: 'nest.py help' will tell you what to do and how to do it + Example: + 'nest.py --user joe@user.com --password swordfish temp 73' + set the temperature to 73 degrees + + 'nest.py --user joe@user.com --password swordfish fan auto' + set the fan to automatic + + Installation: + 'python ./setup.py install' will install nest.py to the right place, + usually your /usr/bin directory. + Licensing: This is distributed unider the Creative Commons 3.0 Non-commecrial, Attribution, Share-Alike license. You can use the code for noncommercial