diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ad2ae15
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+__pycache__
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+MANIFEST
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..7c178cb
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1 @@
+
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..c28ab72
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include README.md
+include LICENSE.md
diff --git a/README.md b/README.md
index d2f934f..ce56564 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,47 @@
- pynest -- a python interface for the Nest Thermostat
- by Scott M Baker, smbaker@gmail.com, http://www.smbaker.com/
+#nest_thermostat
- Usage:
- 'nest.py help' will tell you what to do and how to do it
+**a Python interface for the Nest Thermostat**
+
+*fork of pynest by Scott M Baker, smbaker@gmail.com, http://www.smbaker.com/*
- Example:
- 'nest.py --user joe@user.com --password swordfish temp 73'
- set the temperature to 73 degrees
+##Installation
+`[sudo] pip install nest_thermostat`
- 'nest.py --user joe@user.com --password swordfish fan auto'
- set the fan to automatic
+##Usage
- Installation:
- 'python ./setup.py install' will install nest.py to the right place,
- usually your /usr/bin directory.
+### Module
- 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)
+You can import the module as `nest_thermostat`. Use the source, luke!
- Acknowledgements:
- Chris Burris's Siri Nest Proxy was very helpful to learn the nest's
- authentication and some bits of the protocol.
+Tips: you need to manually call `.login()` first, and `.get_status()` before `.show_*()`
+### Command line
+```
+syntax: nest.py [options] command [command_args]
+options:
+ --user ... username on nest.com
+ --password ... password on nest.com
+ --celsius ... use celsius (the default is farenheit)
+ --serial ... optional, specify serial number of nest to use
+ --index ... optional, 0-based index of nest
+ (use --serial or --index, but not both)
+
+commands:
+ temp ... set target temperature
+ fan [auto|on] ... set fan state
+ mode [cool|heat|range|off] ... set fan state
+ away ... toggle away
+ show ... show everything
+ curtemp ... print current temperature
+ curhumid ... print current humidity
+ curmode ... print current mode
+
+examples:
+ nest.py --user joe@user.com --password swordfish temp 73
+ nest.py --user joe@user.com --password swordfish fan auto
+```
+
+
+---
+
+*Chris Burris's Siri Nest Proxy was very helpful to learn the Nest's authentication and some bits of the protocol.*
diff --git a/nest.py b/nest.py
index 7fcf863..5e523f6 100755
--- a/nest.py
+++ b/nest.py
@@ -1,145 +1,12 @@
#! /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
+"""
+nest.py -- a python interface to the Nest Thermostat
+"""
+
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 nest_thermostat import Nest
def create_parser():
parser = OptionParser(usage="nest [options] command [command_options] [command_args]",
@@ -165,25 +32,29 @@ def create_parser():
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 " --serial ... optional, specify serial number of nest to use"
- print " --index ... optional, 0-based index of nest"
- print " (use --serial or --index, but not both)"
- 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"
+ print( "syntax: nest.py [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( " --serial ... optional, specify serial number of nest to use")
+ print( " --index ... optional, 0-based index of nest")
+ print( " (use --serial or --index, but not both)")
+ print()
+ print( "commands:")
+ print( " temp ... set target temperature")
+ print( " fan [auto|on] ... set fan state")
+ print( " mode [cool|heat|range|off] ... set mode state")
+ print( " away ... toggle away")
+ print( " show ... show everything")
+ print( " curtemp ... print current temperature")
+ print( " curhumid ... print current humidity")
+ print( " curmode ... print current mode")
+ print( " curtarget ... print current target temp")
+ 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()
@@ -194,7 +65,7 @@ def main():
sys.exit(-1)
if (not opts.user) or (not opts.password):
- print "how about specifying a --user and --password option next time?"
+ print( "how about specifying a --user and --password option next time?")
sys.exit(-1)
if opts.celsius:
@@ -210,28 +81,34 @@ def main():
if (cmd == "temp"):
if len(args)<2:
- print "please specify a temperature"
+ 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'"
+ print( "please specify a fan state of 'on' or 'auto'")
sys.exit(-1)
n.set_fan(args[1])
+ elif (cmd == "mode"):
+ if len(args)<2:
+ print( "valid modes are cool, heat, range, and off")
+ sys.exit(-1)
+ n.set_mode(args[1])
+ elif (cmd == "away"):
+ n.toggle_away()
elif (cmd == "show"):
n.show_status()
elif (cmd == "curtemp"):
n.show_curtemp()
+ elif (cmd == "curmode"):
+ n.show_curmode()
+ elif (cmd == "curtarget"):
+ n.show_target()
elif (cmd == "curhumid"):
- print n.status["device"][n.serial]["current_humidity"]
+ print( n.status["device"][n.serial]["current_humidity"])
else:
- print "misunderstood command:", cmd
- print "do 'nest.py help' for help"
+ print( "misunderstood command: %s" % cmd)
+ print( "do 'nest.py help' for help")
if __name__=="__main__":
main()
-
-
-
-
-
diff --git a/nest_thermostat/__init__.py b/nest_thermostat/__init__.py
new file mode 100644
index 0000000..9742c1c
--- /dev/null
+++ b/nest_thermostat/__init__.py
@@ -0,0 +1,149 @@
+#! /usr/bin/python
+
+"""
+nest_thermostat -- 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
+"""
+
+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 = list(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):
+ v = allvars[k] or ''
+ print("%s: %s" % (k, v))
+
+ def show_curtemp(self):
+ temp = self.status["shared"][self.serial]["current_temperature"]
+ temp = self.temp_out(temp)
+
+ print("%0.1f" % temp)
+
+ def show_target(self):
+ temp = self.status["shared"][self.serial]["target_temperature"]
+ temp = self.temp_out(temp)
+
+ print( temp)
+
+ def show_curmode(self):
+ mode = self.status["shared"][self.serial]["target_temperature_type"]
+
+ print( mode)
+
+ 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)
+ })
+
+ def set_mode(self, state):
+ return self._set_shared({
+ "target_temperature_type": str(state)
+ })
+
+ def toggle_away(self):
+ was_away = self.status['structure'][self.structure_id]['away']
+ data = '{"away":%s}' % ('false' if was_away else 'true')
+ response = requests.post(self.transport_url + "/v2/put/structure." + self.structure_id,
+ data = data,
+ headers = {"user-agent":"Nest/1.1.0.10 CFNetwork/548.0.4",
+ "Authorization":"Basic " + self.access_token,
+ "X-nl-protocol-version": "1"})
+ response.raise_for_status()
+ return response
diff --git a/setup.py b/setup.py
index dbd9861..4c4084a 100755
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,17 @@
#!/usr/bin/env python
+#-*- coding:utf-8 -*-
from distutils.core import setup
-setup(name='pynest',
- version='1.0',
- description='Python API for Nest Thermostat',
+setup(name='nest_thermostat',
+ version='1.1',
+ description='Python API and command line tool for talking to the Nestâ„¢ Thermostat',
author='Scott Baker',
author_email='smbaker@gmail.com',
- url='http://www.smbaker.com/',
+ maintainer='Filippo Valsorda',
+ maintainer_email='hi@filippo.io',
+ url='https://github.com/FiloSottile/nest_thermostat/',
scripts=['nest.py'],
- )
+ packages=['nest_thermostat'],
+ install_requires = ['requests']
+)