|
#!/usr/bin/env ruby |
|
|
|
# USAGE |
|
# See : https://github.com/thuehlinger/daemons |
|
# |
|
# ruby nest_poller_control.rb run (foreground) |
|
# ruby nest_poller_control.rb start |
|
# ruby nest_poller_control.rb stop |
|
# ruby nest_poller_control.rb status |
|
|
|
require 'rubygems' |
|
require 'bundler/setup' |
|
require 'daemons' |
|
require 'httparty' |
|
require 'influxdb' |
|
require 'awesome_print' |
|
|
|
Bundler.setup |
|
|
|
# Instructions for getting a long-life access token for API access |
|
# for rempe.us registered API client. |
|
# |
|
# Guide: |
|
# https://developer.nest.com/documentation/cloud/rest-quick-guide |
|
# |
|
# See : https://developer.nest.com/clients for my registered API clients and the URLs. |
|
# |
|
# Step 1 : GET a PIN number |
|
# - Replace STATE with a random number. |
|
# - Paste this in a BROWSER as you will have to click a button to authorize. |
|
# - A PIN code will be returned which you will need for the next step. |
|
# |
|
# GET https://home.nest.com/login/oauth2?client_id=3fb1f2d0-1f87-42f5-a9f8-0b21f142aef5&state=STATE |
|
# |
|
# Step 2 : Copy PIN code. |
|
# e.g. N6B4FV9G |
|
# |
|
# Step 3 : Exchange the authorization PIN code for a long-lived access token |
|
# |
|
# Replace code=AUTHORIZATION_CODE in this URL with the PIN |
|
# |
|
# POST https://api.home.nest.com/oauth2/access_token?client_id=3fb1f2d0-1f87-42f5-a9f8-0b21f142aef5&code=AUTHORIZATION_CODE&client_secret=D1AVcTSab5ERaolisto5uUDIc&grant_type=authorization_code |
|
# |
|
# Step 4 : Extract long-life access token from response which will be used for all future API requests. |
|
# |
|
# {"access_token":"c.7MVm2...","expires_in":315360000} |
|
|
|
NEST_WEATHER_URL = "http://weather.nest.com/weather/v1?query=" |
|
|
|
Daemons.run_proc('nest_poller.rb', {:backtrace => true, :monitor => true, :log_output => true}) do |
|
|
|
# influxdb setup |
|
database = 'nest_poller' |
|
time_precision = 'm' |
|
influxdb = InfluxDB::Client.new(database, |
|
host: ENV['INFLUXDB_NEST_POLLER_HOST'], |
|
username: ENV['INFLUXDB_NEST_POLLER_USERNAME'], |
|
password: ENV['INFLUXDB_NEST_POLLER_PASS']) |
|
|
|
loop do |
|
|
|
# influxdb data collection bucket |
|
data = [] |
|
|
|
begin |
|
api_req = HTTParty.get("https://developer-api.nest.com/?auth=#{ENV['NEST_API_ACCESS_TOKEN']}") |
|
api_resp = JSON.parse(api_req.body) rescue nil |
|
rescue Exception => e |
|
puts "api HTTP req failed : #{e.class} #{e.message}" |
|
api_resp = nil |
|
end |
|
|
|
# report the weather for each home structure on the account. |
|
if api_resp && api_resp["structures"] && api_resp["structures"] && api_resp["structures"].is_a?(Hash) |
|
# Iterate over all structures to get the outdoor weather for it. |
|
api_resp["structures"].keys.each do |id| |
|
postal_code = api_resp["structures"][id]["postal_code"] |
|
# sample : http://weather.nest.com/weather/v1?query=11746 |
|
outdoor_weather_req = HTTParty.get("#{NEST_WEATHER_URL}#{postal_code}") |
|
outdoor_weather_resp = JSON.parse(outdoor_weather_req.body) rescue nil |
|
|
|
if outdoor_weather_resp && outdoor_weather_resp[postal_code] && outdoor_weather_resp[postal_code]["current"] |
|
temp_f = outdoor_weather_resp[postal_code]["current"]["temp_f"] |
|
condition = outdoor_weather_resp[postal_code]["current"]["condition"] |
|
humidity = outdoor_weather_resp[postal_code]["current"]["humidity"] |
|
|
|
weather = {} |
|
weather[:series] = 'weather' |
|
weather[:values] = {temp_f: temp_f, condition: condition, humidity: humidity } |
|
weather[:tags] = {postal_code: postal_code} |
|
data << weather |
|
end |
|
end |
|
else |
|
puts "api_resp['structures'] check failed. : #{api_resp}" |
|
end |
|
|
|
|
|
# Iterate over each thermostat and collect the data for it. |
|
if api_resp && api_resp["devices"] && api_resp["devices"]["thermostats"] && api_resp["devices"]["thermostats"].is_a?(Hash) |
|
api_resp["devices"]["thermostats"].keys.each do |id| |
|
d = api_resp["devices"]["thermostats"][id] |
|
|
|
device = {} |
|
device[:series] = 'nest' |
|
device[:values] = {} |
|
device[:values][:indoor_temp_f] = d["ambient_temperature_f"] |
|
device[:values][:indoor_temp_setpoint] = d["target_temperature_f"] |
|
device[:values][:indoor_humidity] = d["humidity"] |
|
device[:tags] = { device_type: 'nest', |
|
structure_id: d["structure_id"], |
|
structure_name: api_resp["structures"][d["structure_id"]]["name"], |
|
device_id: d["device_id"], |
|
device_location: d["name"], |
|
postal_code: api_resp["structures"][d["structure_id"]]["postal_code"]} |
|
data << device |
|
end |
|
else |
|
puts "api_resp['devices'] check failed. : #{api_resp}" |
|
end |
|
|
|
# write all collected data for structures and thermostats |
|
begin |
|
influxdb.write_points(data, time_precision) |
|
rescue Exception => e |
|
puts "influxdb.write_points req failed : #{e.class} : #{e.message}" |
|
# could not write data points to InfluxDB server. Try again soon. |
|
end |
|
|
|
sleep(60 * 5) # seconds |
|
end |
|
end |
I'm trying to use a container that uses this code, and it looks like something may be broken with the zip code lookup. it's returning blank data, causing the data to not be written to influx. once i hard-coded a zip instead of using the api call in the 2 locations, i was able to get data into influx without error. the error i was getting is:
influxdb.write_points req failed : InfluxDB::Error : {"error":"unable to parse 'nest,device_type=nest,structure_id=MYID,structure_name=Home,device_id=DEVICEID,device_location=LivingRoom,postal_code= indoor_temp_f=69,indoor_temp_setpoint=68,indoor_humidity=25': missing tag value"}