thermostat 0.0.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ === 1.0.0 2010-03-09
2
+
3
+ * major enhancements:
4
+ * Conversion to newgem package for easier future maintenance
5
+ * Make a more sane from end interface
@@ -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
@@ -0,0 +1,5 @@
1
+
2
+ For more information on thermostat.rb, see http://sdaguegems.rubyforge.org/thermostat
3
+
4
+
5
+
@@ -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.
@@ -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]
@@ -0,0 +1,4 @@
1
+ user: admin
2
+ passwd: passwd
3
+ ip: 0.0.0.0
4
+
@@ -0,0 +1,2 @@
1
+ host: sdague@rubyforge.org
2
+ remote_dir: /var/www/gforge-projects/sdaguegems/thermostat
@@ -0,0 +1,2 @@
1
+ host: unknown@rubyforge.org
2
+ remote_dir: /var/www/gforge-projects/thermostat
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
+
@@ -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
@@ -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
- # a little tricky, but not too bad. Basically the access
24
- # gives us either the value off the value map, or the degrees
25
- # reading (as we always get degrees as 10x ints)
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]
@@ -1,95 +1,112 @@
1
- require 'net/http'
2
- require 'uri'
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
- # order here is important
6
- require 'pdp/oid'
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
- @ip = ip
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 @fetched[reading]
25
+ return @network.get(reading)
22
26
  end
23
-
24
- def url
25
- return "http://#{@ip}"
27
+
28
+ # Set a reading for a constant.
29
+ def []=(reading, value)
30
+ return @network.set(reading, value)
26
31
  end
27
-
28
- def set_senors(*array)
29
- @sensors = array
30
- return self
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
- def get_query_url
34
- query = ""
35
- for item in @sensors
36
- if query != ""
37
- query += "&"
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
- query = url + "/get?" + query
45
+ return nil
42
46
  end
43
-
44
- def set_query_url(mod, value)
45
- query = "OID#{mod.oid}=#{value}"
46
- query = url + "/pdp?" + query + "&sumbit=Submit"
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
- # Because of the timing issues with the thermostat we want to get all the data we can in one packet.
50
- def fetch_data()
51
- open(get_query_url, :http_basic_authentication => [@user, @passwd]) do |line|
52
- parse_data(line.read)
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
- return self
69
+ heat_to
55
70
  end
56
71
 
57
- def set_data(mod, value)
58
- unless mod.ro?
59
- open(set_query_url(mod, value), :http_basic_authentication => [@user, @passwd]) do |line|
60
- parse_data(line.read)
61
- end
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
- return self
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
- def parse_data(raw_data)
68
- results = raw_data.split('&')
69
- results.each do |r|
70
- (oid, value) = r.split('=')
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
- def lookup_oid(oid)
79
- newid = oid.sub(/^OID/, '')
80
- @sensors.each do |s|
81
- if s.oid == newid
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
- def to_s
89
- s = ""
90
- @fetched.each do |k, v|
91
- s += "#{k.name}: #{v}\n"
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
+