Permalink
Browse files

The beginnings of the nest gem

  • Loading branch information...
ericboehs committed Jun 5, 2012
1 parent 4f7e385 commit addde26e9e4dc885929e5e7daf4ce8328b477914
Showing with 387 additions and 5 deletions.
  1. +10 −0 .gitignore
  2. +2 −1 Gemfile
  3. +19 −1 Gemfile.lock
  4. +22 −0 LICENSE
  5. +77 −3 README.md
  6. +2 −0 Rakefile
  7. +2 −0 lib/nest_thermostat.rb
  8. +155 −0 lib/nest_thermostat/nest.rb
  9. +3 −0 lib/nest_thermostat/version.rb
  10. +21 −0 nest_thermostat.gemspec
  11. +74 −0 spec/nest_thermostat_spec.rb
View
@@ -1,8 +1,18 @@
*.gem
+*.rbc
.bundle
.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
pkg
+rdoc
spec/reports
test/tmp
test/version_tmp
tmp
+.env
View
@@ -1,3 +1,4 @@
source :rubygems
-gem 'httparty', '~> 0.8.3'
+# Specify your gem's dependencies in nest_thermostat.gemspec
+gemspec
View
@@ -1,14 +1,32 @@
+PATH
+ remote: .
+ specs:
+ nest_thermostat (0.0.1)
+ httparty (~> 0.8.3)
+
GEM
remote: http://rubygems.org/
specs:
+ awesome_print (1.0.2)
+ diff-lcs (1.1.3)
httparty (0.8.3)
multi_json (~> 1.0)
multi_xml
multi_json (1.3.6)
multi_xml (0.5.1)
+ rspec (2.10.0)
+ rspec-core (~> 2.10.0)
+ rspec-expectations (~> 2.10.0)
+ rspec-mocks (~> 2.10.0)
+ rspec-core (2.10.1)
+ rspec-expectations (2.10.0)
+ diff-lcs (~> 1.1.3)
+ rspec-mocks (2.10.1)
PLATFORMS
ruby
DEPENDENCIES
- httparty (~> 0.8.3)
+ awesome_print
+ nest_thermostat!
+ rspec (~> 2.10)
View
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Eric Boehs
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
@@ -1,4 +1,78 @@
-nest-ruby
-=========
+# NestThermostat
-Control your nest thermostat through a ruby library
+This gem allows you to get and set the temperature of your Nest
+thermostat. You can also get and set the away status and get the
+current temperature and target temperature time.
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'nest_thermostat'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install nest_thermostat
+
+## Usage
+Get some useful info:
+```ruby
+@nest = NestThermostat::Nest.new({email: ENV['NEST_EMAIL'], password: ENV['NEST_PASS']})
+puts @nest.current_temperature # => 75.00
+puts @nest.current_temp # => 75.00
+puts @nest.temperature # => 73.00
+puts @nest.temp # => 73.00
+puts @nest.target_temperature_at # => 2012-06-05 14:28:48 +0000 # Ruby date object or false
+puts @nest.target_temp_at # => 2012-06-05 14:28:48 +0000 # Ruby date object or false
+puts @nest.away # => false
+puts @nest.leaf # => true # May take a few seconds after a temp change
+puts @nest.humidity # => 54 # Relative humidity in percent
+```
+
+Change the temperature or away status:
+```ruby
+puts @nest.temperature # => 73.0
+puts @nest.temperature = 74.0
+puts @nest.temperature # => 74.0
+
+puts @nest.away # => false
+puts @nest.away = true
+puts @nest.away # => true
+```
+
+Default temperatures are in fahrenheit but you can change to celsius or kelvin:
+```ruby
+@nest = NestThermostat::Nest.new({..., temperature_scale: 'c'}) # Or C, Celsius or celsius
+@nest.temperature_scale = 'k' # or K, Kelvin or kelvin
+```
+
+And of course if you want to get LOTS of other goodies like (schedule and every diag piece of info you'd ever want):
+```ruby
+p @nest.status
+
+# -- OR --
+
+require 'yaml'
+yaml @nest.status
+
+# -- OR my favorite --
+
+require 'ap' # gem install awesome_print
+ap @nest.status
+```
+Feel free to implement anything you see useful and submit a pull
+request. I'd love to see other information like scheduling or multiple
+device/location support added.
+
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
View
@@ -0,0 +1,2 @@
+#!/usr/bin/env rake
+require "bundler/gem_tasks"
View
@@ -0,0 +1,2 @@
+require "nest_thermostat/version"
+require "nest_thermostat/nest"
View
@@ -0,0 +1,155 @@
+require 'rubygems'
+require 'httparty'
+require 'json'
+require 'uri'
+
+module NestThermostat
+ class Nest
+ attr_accessor :email, :password, :login_url, :user_agent, :auth,
+ :temperature_scale, :login, :token, :user_id, :transport_url,
+ :transport_host, :structure_id, :device_id, :headers
+
+ def initialize(config = {})
+ raise 'Please specify your nest email' unless config[:email]
+ raise 'Please specify your nest password' unless config[:password]
+
+ # User specified information
+ self.email = config[:email]
+ self.password = config[:password]
+ self.temperature_scale = config[:temperature_scale] || config[:temp_scale] || 'f'
+ self.login_url = config[:login_url] || 'https://home.nest.com/user/login'
+ self.user_agent = config[:user_agent] ||'Nest/1.1.0.10 CFNetwork/548.0.4'
+
+ # Login and get token, user_id and URLs
+ login
+ self.token = login["access_token"]
+ self.user_id = login["userid"]
+ self.transport_url = login["urls"]["transport_url"]
+ self.transport_host = URI.parse(self.transport_url).host
+ self.headers = {
+ 'Host' => self.transport_host,
+ 'User-Agent' => self.user_agent,
+ 'Authorization' => 'Basic ' + self.token,
+ 'X-nl-user-id' => self.user_id,
+ 'X-nl-protocol-version' => '1',
+ 'Accept-Language' => 'en-us',
+ 'Connection' => 'keep-alive',
+ 'Accept' => '*/*'
+ }
+
+ # Set device and structure id
+ status
+ end
+
+ def status
+ request = HTTParty.get("#{self.transport_url}/v2/mobile/user.#{self.user_id}", headers: self.headers) rescue nil
+ result = JSON.parse(request.body) rescue nil
+
+ self.structure_id = result['user'][user_id]['structures'][0].split('.')[1]
+ self.device_id = result['structure'][structure_id]['devices'][0].split('.')[1]
+
+ result
+ end
+
+ def leaf
+ status["device"][self.device_id]["leaf"]
+ end
+
+ def humidity
+ status["device"][self.device_id]["current_humidity"]
+ end
+
+ def current_temperature
+ convert_temp_for_get(status["shared"][self.device_id]["current_temperature"])
+ end
+ alias_method :current_temp, :current_temperature
+
+ def temperature
+ convert_temp_for_get(status["shared"][self.device_id]["target_temperature"])
+ end
+ alias_method :temp, :temperature
+
+ def temperature=(degrees)
+ degrees = convert_temp_for_set(degrees)
+
+ request = HTTParty.post(
+ "#{self.transport_url}/v2/put/shared.#{self.device_id}",
+ body: %Q({"target_change_pending":true,"target_temperature":#{degrees}}),
+ headers: self.headers
+ ) rescue nil
+ end
+ alias_method :temp=, :temperature=
+
+ def target_temperature_at
+ epoch = status["device"][self.device_id]["time_to_target"]
+ epoch != 0 ? Time.at(epoch) : false
+ end
+ alias_method :target_temp_at, :target_temperature_at
+
+ def away
+ status["structure"][structure_id]["away"]
+ end
+
+ def away=(state)
+ request = HTTParty.post(
+ "#{self.transport_url}/v2/put/structure.#{self.structure_id}",
+ body: %Q({"away_timestamp":#{Time.now.to_i},"away":#{!!state},"away_setter":0}),
+ headers: self.headers
+ ) rescue nil
+ end
+
+ def temp_scale=(scale)
+ self.temperature_scale = scale
+ end
+
+ private
+ def login
+ login_request = HTTParty.post(
+ self.login_url,
+ body: { username: self.email, password: self.password },
+ headers: { 'User-Agent' => self.user_agent }
+ )
+
+ self.auth = JSON.parse(login_request.body) rescue nil
+ end
+
+ def convert_temp_for_get(degrees)
+ case self.temperature_scale
+ when /f|F|(F|f)ahrenheit/
+ c2f(degrees).round(3)
+ when /k|K|(K|k)elvin/
+ c2k(degrees).round(3)
+ else
+ degrees
+ end
+ end
+
+ def convert_temp_for_set(degrees)
+ case self.temperature_scale
+ when /f|F|(F|f)ahrenheit/
+ f2c(degrees).round(5)
+ when /k|K|(K|k)elvin/
+ k2c(degrees).round(5)
+ else
+ degrees
+ end
+ end
+
+ def k2c(degrees)
+ degrees.to_f - 273.15
+ end
+
+ def c2k(degrees)
+ degrees.to_f + 273.15
+ end
+
+ def c2f(degrees)
+ degrees.to_f * 9.0 / 5 + 32
+ end
+
+ def f2c(degrees)
+ (degrees.to_f - 32) * 5 / 9
+ end
+
+ end
+end
@@ -0,0 +1,3 @@
+module NestThermostat
+ VERSION = "0.0.1"
+end
View
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+require File.expand_path('../lib/nest_thermostat/version', __FILE__)
+
+Gem::Specification.new do |gem|
+ gem.authors = ["Eric Boehs"]
+ gem.email = ["ericboehs@gmail.com"]
+ gem.description = %q{Control your nest thermostat}
+ gem.summary = %q{View and set temperature and away status for your Nest}
+ gem.homepage = "http://github.com/ericboehs/nest-ruby"
+
+ gem.files = `git ls-files`.split($\)
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
+ gem.name = "nest_thermostat"
+ gem.require_paths = ["lib"]
+ gem.version = NestThermostat::VERSION
+ gem.add_dependency "httparty", "~> 0.8.3"
+
+ gem.add_development_dependency "rspec", "~> 2.10"
+ gem.add_development_dependency "awesome_print"
+end
Oops, something went wrong.

0 comments on commit addde26

Please sign in to comment.