thermostat 0.0.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/Manifest.txt +29 -0
- data/PostInstall.txt +5 -0
- data/README.rdoc +66 -0
- data/Rakefile +28 -0
- data/account.yaml.example +4 -0
- data/config/website.yml +2 -0
- data/config/website.yml.sample +2 -0
- data/docs/PDP_API_R1_11.pdf +0 -0
- data/examples/test-fan-off.rb +20 -0
- data/examples/test-fan-on.rb +20 -0
- data/examples/test-run.rb +21 -0
- data/examples/test-set-heat.rb +20 -0
- data/lib/pdp/network.rb +123 -0
- data/lib/pdp/oid.rb +13 -4
- data/lib/thermostat.rb +87 -70
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +71 -0
- data/test/test_helper.rb +16 -0
- data/test/test_oid.rb +12 -0
- data/test/test_thermostat.rb +26 -0
- data/website/index.html +74 -0
- data/website/index.txt +47 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +159 -0
- data/website/template.html.erb +50 -0
- metadata +123 -37
- data/README +0 -36
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
account.yaml.example
|
7
|
+
config/website.yml
|
8
|
+
config/website.yml.sample
|
9
|
+
docs/PDP_API_R1_11.pdf
|
10
|
+
examples/test-fan-off.rb
|
11
|
+
examples/test-fan-on.rb
|
12
|
+
examples/test-run.rb
|
13
|
+
examples/test-set-heat.rb
|
14
|
+
lib/pdp/constants.rb
|
15
|
+
lib/pdp/network.rb
|
16
|
+
lib/pdp/oid.rb
|
17
|
+
lib/thermostat.rb
|
18
|
+
script/console
|
19
|
+
script/destroy
|
20
|
+
script/generate
|
21
|
+
script/txt2html
|
22
|
+
test/test_helper.rb
|
23
|
+
test/test_oid.rb
|
24
|
+
test/test_thermostat.rb
|
25
|
+
website/index.html
|
26
|
+
website/index.txt
|
27
|
+
website/javascripts/rounded_corners_lite.inc.js
|
28
|
+
website/stylesheets/screen.css
|
29
|
+
website/template.html.erb
|
data/PostInstall.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= thermostat
|
2
|
+
|
3
|
+
* http://github.com/sdague/thermostat.rb
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Thermostat.rb is an attempt to build an easy to use ruby api around
|
8
|
+
the web services provided by the Proliphix line of network
|
9
|
+
thermostats. Their API is documented at
|
10
|
+
http://www.proliphix.com/Documentation.aspx.
|
11
|
+
|
12
|
+
The code is tested on the NT20e unit, which is what I own. It should
|
13
|
+
react that same across all their network accessible units.
|
14
|
+
|
15
|
+
== FEATURES/PROBLEMS:
|
16
|
+
|
17
|
+
Note: It wrote the base module over a year ago, and am now starting to more actively use it in a new project, so interfaces may change to make the api easier to use.
|
18
|
+
|
19
|
+
Important notices from the manual:
|
20
|
+
|
21
|
+
**Limitations on the Frequency of Operations**
|
22
|
+
|
23
|
+
While the device can handle numerous back to back requests, it is
|
24
|
+
advisable to not sustain an operation frequency higher than 1 request
|
25
|
+
per 60 second period for a substantial amount of time. In other words,
|
26
|
+
sustained polling of the device should not exceed a few requests per
|
27
|
+
minute. A prolonged burst of requests higher than 1 request per 60
|
28
|
+
second period may degrade the main function of the thermostat. Also,
|
29
|
+
if one is making a PIB set and wishes to observe a reaction from the
|
30
|
+
device, it is advisable to wait for at least 1 second between the set
|
31
|
+
request and subsequent get request.
|
32
|
+
|
33
|
+
== SYNOPSIS:
|
34
|
+
|
35
|
+
FIX (code sample of usage)
|
36
|
+
|
37
|
+
== REQUIREMENTS:
|
38
|
+
|
39
|
+
== INSTALL:
|
40
|
+
|
41
|
+
sudo gem install thermostat
|
42
|
+
|
43
|
+
== LICENSE:
|
44
|
+
|
45
|
+
(The MIT License)
|
46
|
+
|
47
|
+
Copyright (c) 2010 Sean Dague
|
48
|
+
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
50
|
+
a copy of this software and associated documentation files (the
|
51
|
+
'Software'), to deal in the Software without restriction, including
|
52
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
53
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
54
|
+
permit persons to whom the Software is furnished to do so, subject to
|
55
|
+
the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be
|
58
|
+
included in all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
61
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
62
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
63
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
64
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
65
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
66
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/thermostat'
|
6
|
+
|
7
|
+
ENV['VERSION'] = Thermostat::VERSION
|
8
|
+
|
9
|
+
Hoe.plugin :newgem
|
10
|
+
Hoe.plugin :website
|
11
|
+
# Hoe.plugin :cucumberfeatures
|
12
|
+
|
13
|
+
# Generate all the Rake tasks
|
14
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
15
|
+
$hoe = Hoe.spec 'thermostat' do
|
16
|
+
self.developer 'Sean Dague', 'sean@dague.net'
|
17
|
+
self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
18
|
+
self.rubyforge_name = 'sdaguegems'
|
19
|
+
self.extra_deps = [['temperature', '>= 1.0.0']]
|
20
|
+
# self.extra_deps = [['activesupport','>= 2.0.2']]
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'newgem/tasks'
|
24
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
25
|
+
|
26
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
27
|
+
# remove_task :default
|
28
|
+
# task :default => [:spec, :features]
|
data/config/website.yml
ADDED
Binary file
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift "../lib"
|
4
|
+
|
5
|
+
require "yaml"
|
6
|
+
require 'thermostat'
|
7
|
+
include Proliphix
|
8
|
+
|
9
|
+
c = YAML.load_file("../account.yaml")
|
10
|
+
|
11
|
+
t = Thermostat.new(c["ip"], c["user"], c["passwd"])
|
12
|
+
|
13
|
+
t.set_senors(ThermHvacMode, ThermHvacState, ThermFanState, ThermFanMode, ThermAverageTemp, ThermHeat1Usage)
|
14
|
+
t.fetch_data
|
15
|
+
|
16
|
+
puts t
|
17
|
+
|
18
|
+
t.set_data(ThermFanMode, 1)
|
19
|
+
|
20
|
+
puts t
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift "../lib"
|
4
|
+
|
5
|
+
require "yaml"
|
6
|
+
require 'thermostat'
|
7
|
+
|
8
|
+
include Proliphix
|
9
|
+
|
10
|
+
c = YAML.load_file("../account.yaml")
|
11
|
+
|
12
|
+
t = Thermostat.new(c["ip"], c["user"], c["passwd"])
|
13
|
+
|
14
|
+
t.set_senors(ThermHvacMode, ThermHvacState, ThermFanState, ThermFanMode, ThermAverageTemp, ThermHeat1Usage)
|
15
|
+
t.fetch_data
|
16
|
+
|
17
|
+
puts t
|
18
|
+
|
19
|
+
t.set_data(ThermFanMode, 2)
|
20
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift "../lib"
|
4
|
+
|
5
|
+
require "yaml"
|
6
|
+
require 'thermostat'
|
7
|
+
|
8
|
+
include Proliphix
|
9
|
+
|
10
|
+
c = YAML.load_file("../account.yaml")
|
11
|
+
|
12
|
+
t = Thermostat.new(c["ip"], c["user"], c["passwd"])
|
13
|
+
|
14
|
+
t.set_senors(ThermHvacMode, ThermHvacState, ThermFanState, ThermFanMode, ThermAverageTemp, ThermHeat1Usage, ThermSetbackHeat)
|
15
|
+
|
16
|
+
t.fetch_data
|
17
|
+
|
18
|
+
puts t
|
19
|
+
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift "../lib"
|
4
|
+
|
5
|
+
require "yaml"
|
6
|
+
require 'thermostat'
|
7
|
+
include Proliphix
|
8
|
+
|
9
|
+
c = YAML.load_file("../account.yaml")
|
10
|
+
|
11
|
+
t = Thermostat.new(c["ip"], c["user"], c["passwd"])
|
12
|
+
|
13
|
+
t.set_senors(ThermHvacMode, ThermHvacState, ThermFanState, ThermFanMode, ThermAverageTemp, ThermHeat1Usage, ThermSetbackHeat)
|
14
|
+
t.fetch_data
|
15
|
+
|
16
|
+
puts t
|
17
|
+
|
18
|
+
# Sets the heat to 68.0 F
|
19
|
+
t.set_data(ThermSetbackHeat, 680)
|
20
|
+
|
data/lib/pdp/network.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
module Proliphix
|
2
|
+
# This is the core networking module for getting data off the
|
3
|
+
# thermostat. It looks sort of funny because I am reusing
|
4
|
+
# constants as hash keys which are actually objects.
|
5
|
+
#
|
6
|
+
require "open-uri"
|
7
|
+
|
8
|
+
class Network
|
9
|
+
def initialize(ip, user, passwd)
|
10
|
+
@ip = ip
|
11
|
+
@user = user
|
12
|
+
@passwd = passwd
|
13
|
+
@last = Time.now.to_i
|
14
|
+
@fetched = {}
|
15
|
+
@sensors = [
|
16
|
+
ThermHvacMode,
|
17
|
+
ThermHvacState,
|
18
|
+
ThermFanMode,
|
19
|
+
ThermFanState,
|
20
|
+
ThermSetbackHeat,
|
21
|
+
ThermSetbackCool,
|
22
|
+
ThermHeat1Usage,
|
23
|
+
ThermCool1Usage,
|
24
|
+
ThermFanUsage,
|
25
|
+
ThermAverageTemp
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def [](reading)
|
31
|
+
return @fetched[reading]
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(name)
|
35
|
+
if self[name] and up_to_date
|
36
|
+
return self[name]
|
37
|
+
else
|
38
|
+
fetch_data
|
39
|
+
# TODO: what if we don't manage to return values
|
40
|
+
return self[name]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(name, value)
|
45
|
+
set_data(name, value)
|
46
|
+
sleep 1
|
47
|
+
fetch_data
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Are we currently up to date with our data, if so don't worry
|
53
|
+
# about getting any other values.
|
54
|
+
def up_to_date
|
55
|
+
return (@last + 60) > Time.now.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
def url
|
59
|
+
return "http://#{@ip}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_query_url
|
63
|
+
query = ""
|
64
|
+
for item in @sensors
|
65
|
+
if query != ""
|
66
|
+
query += "&"
|
67
|
+
end
|
68
|
+
query += "OID#{item.oid}="
|
69
|
+
end
|
70
|
+
query = url + "/get?" + query
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_query_url(mod, value)
|
74
|
+
query = "OID#{mod.oid}=#{value}"
|
75
|
+
query = url + "/pdp?" + query + "&sumbit=Submit"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Because of the timing issues with the thermostat we want to
|
79
|
+
# get all the data we can in one packet.
|
80
|
+
def fetch_data()
|
81
|
+
open(get_query_url, :http_basic_authentication => [@user, @passwd]) do |line|
|
82
|
+
parse_data(line.read)
|
83
|
+
end
|
84
|
+
return self
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_data(mod, value)
|
88
|
+
unless mod.ro?
|
89
|
+
open(set_query_url(mod, value), :http_basic_authentication => [@user, @passwd]) do |line|
|
90
|
+
parse_data(line.read)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
return self
|
94
|
+
end
|
95
|
+
|
96
|
+
# The return for the data from Proliphix is kind of silly. It
|
97
|
+
# includes oids = values in what looks like a uri string. So
|
98
|
+
# split twice to get values.
|
99
|
+
def parse_data(raw_data)
|
100
|
+
results = raw_data.split('&')
|
101
|
+
results.each do |r|
|
102
|
+
(oid, value) = r.split('=')
|
103
|
+
mod = lookup_oid(oid)
|
104
|
+
if mod
|
105
|
+
@fetched[mod] = mod[value]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
@last = Time.now.to_i
|
109
|
+
end
|
110
|
+
|
111
|
+
def lookup_oid(oid)
|
112
|
+
newid = oid.sub(/^OID/, '')
|
113
|
+
@sensors.each do |s|
|
114
|
+
if s.oid == newid
|
115
|
+
return s
|
116
|
+
end
|
117
|
+
end
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
data/lib/pdp/oid.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module Proliphix
|
2
|
+
# This is the basic encapsulation class for OIDs used by the Proliphix thermostat.
|
3
|
+
|
2
4
|
class OID
|
3
5
|
def initialize(name, oid, readonly, valuemap = nil, scale = true)
|
4
6
|
@name = name
|
@@ -8,22 +10,29 @@ module Proliphix
|
|
8
10
|
@scale = scale
|
9
11
|
end
|
10
12
|
|
13
|
+
# return the oid of the item that is used in the proliphix web
|
14
|
+
# service calls. This is typically of the format '4.1.12' or
|
15
|
+
# '4.5.2'
|
11
16
|
def oid
|
12
17
|
return @oid
|
13
18
|
end
|
14
19
|
|
20
|
+
# return the name which you've provided. This should be a basic string.
|
15
21
|
def name
|
16
22
|
return @name
|
17
23
|
end
|
18
24
|
|
25
|
+
# is the data from the oid read only.
|
19
26
|
def ro?
|
20
27
|
return @readonly
|
21
28
|
end
|
22
29
|
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
30
|
+
# Make the OIDs act as their own translator for the values
|
31
|
+
# that come out of the webservices interface. This is done
|
32
|
+
# via the array opperator because it's doable.
|
33
|
+
#
|
34
|
+
# We more or less get the raw value or the value divided by
|
35
|
+
# 10, as degrees come back as a 10x integer.
|
27
36
|
def [](value)
|
28
37
|
if @valuemap
|
29
38
|
return @valuemap[value.to_i]
|
data/lib/thermostat.rb
CHANGED
@@ -1,95 +1,112 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'open-uri'
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
4
3
|
|
5
|
-
#
|
6
|
-
|
7
|
-
require 'pdp/constants'
|
8
|
-
|
9
|
-
# Some docs
|
4
|
+
# This is the main class for controlling the Proliphix Thermostat.
|
5
|
+
# You must first initialize it with the control point credentials
|
10
6
|
class Thermostat
|
11
|
-
|
7
|
+
VERSION = '1.0.0'
|
8
|
+
|
9
|
+
require 'pdp/oid'
|
10
|
+
require 'pdp/constants'
|
11
|
+
require 'pdp/network'
|
12
|
+
|
13
|
+
include Proliphix
|
14
|
+
|
15
|
+
# create a new thermostat object. It needs
|
16
|
+
# * ip - the ip address of the thermostat, probably 192.168.1.2
|
17
|
+
# * user - the admin user for the thermostat
|
18
|
+
# * passwd - the admin passwd for the thermostat
|
12
19
|
def initialize(ip, user, passwd)
|
13
|
-
@
|
14
|
-
@user = user
|
15
|
-
@passwd = passwd
|
16
|
-
@fetched = {}
|
17
|
-
@sensors = []
|
20
|
+
@network = Proliphix::Network.new(ip, user, passwd)
|
18
21
|
end
|
19
|
-
|
22
|
+
|
23
|
+
# Get a reading for a constant, returns raw values
|
20
24
|
def [](reading)
|
21
|
-
return @
|
25
|
+
return @network.get(reading)
|
22
26
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
|
28
|
+
# Set a reading for a constant.
|
29
|
+
def []=(reading, value)
|
30
|
+
return @network.set(reading, value)
|
26
31
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
32
|
+
|
33
|
+
# Get the thermostat mode. This will come back as one of "Off",
|
34
|
+
# "Heat", "Cool", or "Auto".
|
35
|
+
def mode
|
36
|
+
self[ThermHvacMode]
|
31
37
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
query += "OID#{item.oid}="
|
38
|
+
|
39
|
+
# Return what temperature the thermostat is attempting to heat to.
|
40
|
+
def heat_to
|
41
|
+
case mode
|
42
|
+
when "Heat" then return self[ThermSetbackHeat]
|
43
|
+
when "Auto" then return self[ThermSetbackHeat]
|
40
44
|
end
|
41
|
-
|
45
|
+
return nil
|
42
46
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
|
48
|
+
# Return what temperature the thermostat is attempting to cool to.
|
49
|
+
def cool_to
|
50
|
+
case mode
|
51
|
+
when "Cool" then return self[ThermSetbackCool]
|
52
|
+
when "Auto" then return self[ThermSetbackCool]
|
53
|
+
end
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return the current temperature reading on the thermostat
|
58
|
+
def temp
|
59
|
+
self[ThermAverageTemp]
|
47
60
|
end
|
48
61
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
|
62
|
+
# Set the target heat-to temperature. The effects in non heating
|
63
|
+
# mode aren't well defined.
|
64
|
+
def heat_to=(value)
|
65
|
+
value *= 10
|
66
|
+
if (value > 500) and (value < 800)
|
67
|
+
self[ThermSetbackHeat] = value.to_i
|
53
68
|
end
|
54
|
-
|
69
|
+
heat_to
|
55
70
|
end
|
56
71
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
72
|
+
# Set the target cool-to temperature. The effects in non cooling
|
73
|
+
# mode aren't well defined.
|
74
|
+
def cool_to=(value)
|
75
|
+
value *= 10
|
76
|
+
if (value > 600) and (value < 900)
|
77
|
+
self[ThermSetbackCool] = value.to_i
|
62
78
|
end
|
63
|
-
|
79
|
+
cool_to
|
80
|
+
end
|
81
|
+
|
82
|
+
# Are we currently running the heat, returns a boolean
|
83
|
+
def heating?
|
84
|
+
self[ThermHvacState] == "Heat"
|
64
85
|
end
|
65
86
|
|
87
|
+
# Are we currently running the AC, returns a boolean
|
88
|
+
def cooling?
|
89
|
+
self[ThermHvacState] == "Cool"
|
90
|
+
end
|
66
91
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
mod = lookup_oid(oid)
|
72
|
-
if mod
|
73
|
-
@fetched[mod] = mod[value]
|
74
|
-
end
|
75
|
-
end
|
92
|
+
# Is the fan currently on. It doesn't matter why it's on, just
|
93
|
+
# that it's on.
|
94
|
+
def fan_on?
|
95
|
+
self[ThermFanState] == "On"
|
76
96
|
end
|
77
97
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
return s
|
83
|
-
end
|
84
|
-
end
|
85
|
-
return nil
|
98
|
+
# Turn the fan on
|
99
|
+
def fan_on!
|
100
|
+
self[ThermFanMode] = 2
|
101
|
+
self[ThermFanState]
|
86
102
|
end
|
87
103
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
end
|
93
|
-
return s
|
104
|
+
# Turn the fan off
|
105
|
+
def fan_off!
|
106
|
+
self[ThermFanMode] = 1
|
107
|
+
self[ThermFanState]
|
94
108
|
end
|
95
109
|
end
|
110
|
+
|
111
|
+
|
112
|
+
|