saal 0.2.2

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,112 @@
1
+ require File.dirname(__FILE__)+'/test_helper.rb'
2
+ require 'webrick'
3
+ #require 'webrick/accesslog'
4
+
5
+ class TestDINRelay < Test::Unit::TestCase
6
+ SERVICE_OPTS = {:host => 'localhost', :port => 33333,
7
+ :user => "someuser", :pass =>"somepass"}
8
+
9
+ class BasicServing < WEBrick::HTTPServlet::AbstractServlet
10
+ def self.get_instance(config, opts)
11
+ new(opts)
12
+ end
13
+ def initialize(opts)
14
+ @html = opts[:html]
15
+ @user = opts[:user]
16
+ @pass = opts[:pass]
17
+ @feedback = opts[:feedback] || {}
18
+ end
19
+ def do_GET(req, res)
20
+ @feedback[:uri] = req.request_uri.to_s
21
+ WEBrick::HTTPAuth.basic_auth(req, res, "My Realm") {|user, pass|
22
+ user == @user && pass == @pass
23
+ }
24
+ res.body = @html
25
+ res['Content-Type'] = "text/xml"
26
+ end
27
+ end
28
+
29
+ def with_webrick(opts)
30
+ opts = SERVICE_OPTS.merge(opts)
31
+
32
+ Socket.do_not_reverse_lookup = true # Speed up startup
33
+ log = WEBrick::Log.new($stderr, WEBrick::Log::ERROR)
34
+ access_log = [[log, WEBrick::AccessLog::COMBINED_LOG_FORMAT]]
35
+ s = WEBrick::HTTPServer.new(:Port => opts[:port],
36
+ :Logger => log,
37
+ :AccessLog => access_log)
38
+ s.mount('/', BasicServing, opts.merge(:feedback => (f = {})))
39
+
40
+ thread = Thread.new do
41
+ s.start
42
+ end
43
+ while s.status != :Running; sleep 0.1; end # Make sure the server is up
44
+ yield f
45
+ s.shutdown
46
+ thread.exit
47
+ end
48
+
49
+ def create_index_html(hash)
50
+ outlets = hash
51
+ erb = ERB.new(File.open(File.dirname(__FILE__)+'/dinrelay.html.erb').read)
52
+ erb.result(binding)
53
+ end
54
+
55
+ def setup
56
+ @og=SAAL::DINRelay::OutletGroup.new(SERVICE_OPTS)
57
+ @vals={1=>"OFF",2=>"OFF",3=>"ON",4=>"OFF",5=>"ON",6=>"ON",7=>"ON",8=>"OFF"}
58
+ @rvals={1=>"ON",2=>"ON",3=>"OFF",4=>"ON",5=>"OFF",6=>"OFF",7=>"OFF",8=>"ON"}
59
+ end
60
+
61
+ def assert_path(path, uri)
62
+ assert_equal "http://localhost:#{SERVICE_OPTS[:port]}"+path, uri
63
+ end
64
+
65
+ def test_read_state
66
+ with_webrick(:html=>create_index_html(@vals)) do |feedback|
67
+ @vals.each do |num, state|
68
+ assert_equal state, @og.state(num)
69
+ assert_path '/index.htm', feedback[:uri]
70
+ end
71
+ end
72
+ end
73
+
74
+ def test_set_state
75
+ with_webrick(:html=>create_index_html(@rvals)) do |feedback|
76
+ @vals.each do |num, state|
77
+ newstate = state == "ON" ? "OFF" : "ON"
78
+ assert @og.set_state(num,newstate), "State change not working"
79
+ assert_path "/outlet?#{num}=#{newstate}", feedback[:uri]
80
+ end
81
+ end
82
+ end
83
+
84
+ def test_enumerate_sensors
85
+ sensors = SAAL::Sensors.new(TEST_SENSORS_DINRELAY_FILE, TEST_DBCONF)
86
+ assert_equal((1..8).map{|i| "name#{i}"}, sensors.map{|s| s.name}.sort)
87
+ end
88
+
89
+ def test_read_sensors
90
+ sensors = SAAL::Sensors.new(TEST_SENSORS_DINRELAY_FILE, TEST_DBCONF)
91
+ with_webrick(:html=>create_index_html(@vals)) do |feedback|
92
+ @vals.each do |num, state|
93
+ value = state == "ON" ? 1.0 : 0.0
94
+ assert_equal value, sensors.send('name'+num.to_s).read
95
+ assert_path '/index.htm', feedback[:uri]
96
+ end
97
+ end
98
+ end
99
+
100
+ def test_set_sensors
101
+ sensors = SAAL::Sensors.new(TEST_SENSORS_DINRELAY_FILE, TEST_DBCONF)
102
+ with_webrick(:html=>create_index_html(@rvals)) do |feedback|
103
+ @vals.each do |num, state|
104
+ newval = state == "ON" ? 0.0 : 1.0
105
+ newstate = state == "ON" ? "OFF" : "ON"
106
+ assert_equal newval, sensors.send('name'+num.to_s).write(newval),
107
+ "State change not working"
108
+ assert_path "/outlet?#{num}=#{newstate}", feedback[:uri]
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,5 @@
1
+ non_existant:
2
+ name: "A non-existant sensor"
3
+ onewire:
4
+ serial: /10.A00000000000/temperature
5
+
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__)+'/test_helper.rb'
2
+
3
+ class TestOutlierCache < Test::Unit::TestCase
4
+ def test_clean_setup
5
+ cache = SAAL::OutlierCache.new
6
+ assert !cache.live, "Live empty cache"
7
+ 20.times {cache.validate(10)}
8
+ assert cache.live, "Not Live full cache"
9
+ end
10
+
11
+ def test_failed_dirty_setup
12
+ cache = SAAL::OutlierCache.new
13
+ assert !cache.live, "Live empty cache"
14
+ (1..20).each {|i| cache.validate(i)}
15
+ assert !cache.live, "Live cache with dirty results"
16
+ end
17
+
18
+ def test_working_dirty_setup
19
+ cache = SAAL::OutlierCache.new
20
+ assert !cache.live, "Live empty cache"
21
+ [1,1,1,2,1,1,1,1,1,1,1,1,1].each {|i| cache.validate(i)}
22
+ assert cache.live, "Not Live full cache"
23
+ end
24
+
25
+ def test_validate
26
+ cache = SAAL::OutlierCache.new
27
+ 20.times {cache.validate(10)}
28
+ assert cache.live, "Not Live full cache"
29
+ assert cache.validate(10), "Non validated good value"
30
+ assert !cache.validate(15), "Validated bad value"
31
+ end
32
+
33
+ def test_setup_recover_from_discontinuity
34
+ cache = SAAL::OutlierCache.new
35
+ 20.times {cache.validate(10)}
36
+ assert cache.live, "Not Live full cache"
37
+ 20.times {cache.validate(15)}
38
+ assert cache.live, "Not Live full cache"
39
+ assert cache.validate(15), "Non validated good value"
40
+ assert !cache.validate(10), "Validated bad value"
41
+ end
42
+ end
@@ -0,0 +1,109 @@
1
+ require File.dirname(__FILE__)+'/test_helper.rb'
2
+
3
+ class MockConnection
4
+ attr_accessor :value, :values
5
+ def initialize
6
+ @value = @values = nil
7
+ end
8
+ def read(serial)
9
+ @value ? @value : @values.shift
10
+ end
11
+ end
12
+
13
+ class MockDBStore
14
+ attr_accessor :value
15
+ def average(sensor, from, to)
16
+ @value
17
+ end
18
+ end
19
+
20
+ class TestSensor < Test::Unit::TestCase
21
+ def fake_sensor(name, opts={})
22
+ SAAL::Sensors.sensors_from_defs(@dbstore, name, @defs[name],
23
+ opts.merge(:owconn => @conn))[0]
24
+ end
25
+
26
+ def setup
27
+ @defs = YAML::load File.new(TEST_SENSOR_CLEANUPS_FILE)
28
+ @conn = MockConnection.new
29
+ @dbstore = MockDBStore.new
30
+ @fake = fake_sensor('fake', :no_outliercache => true)
31
+ @fake2 = fake_sensor('fake2', :no_outliercache => true)
32
+ @fake3 = fake_sensor('fake3')
33
+ @max_value = @defs['fake2']['max_value']
34
+ @max_correctable = @defs['fake2']['max_correctable']
35
+ @min_value = @defs['fake2']['min_value']
36
+ @min_correctable = @defs['fake2']['min_correctable']
37
+ end
38
+
39
+ def test_read_too_high_values
40
+ @conn.value = @max_value+1
41
+ assert_nil @fake.read
42
+ assert_nil @fake.read_uncached
43
+ @conn.value = @max_value
44
+ assert_equal @max_value, @fake.read
45
+ assert_equal @max_value, @fake.read_uncached
46
+ end
47
+
48
+ def test_read_too_high_but_correctable_values
49
+ @conn.value = @max_correctable
50
+ assert_equal @max_value, @fake2.read
51
+ assert_equal @max_value, @fake2.read_uncached
52
+ @conn.value = @max_correctable+1
53
+ assert_nil @fake2.read
54
+ assert_nil @fake2.read_uncached
55
+ end
56
+
57
+ def test_read_too_low_values
58
+ @conn.value = @min_value-1
59
+ assert_nil @fake.read
60
+ assert_nil @fake.read_uncached
61
+ @conn.value = @min_value
62
+ assert_equal @min_value, @fake.read
63
+ assert_equal @min_value, @fake.read_uncached
64
+ end
65
+
66
+ def test_read_too_low_but_correctable_values
67
+ @conn.value = @min_correctable
68
+ assert_equal @min_value, @fake2.read
69
+ assert_equal @min_value, @fake2.read_uncached
70
+ @conn.value = @min_correctable-1
71
+ assert_nil @fake2.read
72
+ assert_nil @fake2.read_uncached
73
+ end
74
+
75
+ def test_read_without_limits
76
+ @conn.value = 200
77
+ assert_equal 200, @fake3.read
78
+ assert_equal 200, @fake3.read_uncached
79
+ end
80
+
81
+ def test_eliminate_outliers
82
+ @conn.values = [200]*20 + [1000,200]
83
+ assert_equal [200]*21, (1..21).map{@fake3.read}
84
+ @conn.values = [200]*20 + [1000,200,1000,200]
85
+ assert_equal [200]*21, (1..21).map{@fake3.read_uncached}
86
+ end
87
+
88
+ def test_eliminate_outliers_zeroes
89
+ @conn.values = [0]*20 + [1000,0]
90
+ assert_equal [0]*20+[1000], (1..21).map{@fake3.read}
91
+ @conn.values = [0]*20 + [1000,0]
92
+ assert_equal [0]*20+[1000], (1..21).map{@fake3.read_uncached}
93
+ end
94
+
95
+ def test_mocked
96
+ @mockable = fake_sensor('fake3')
97
+ @conn.value = 1.0
98
+ assert_equal 1.0, @mockable.read
99
+ @mockable.mock_set(:value => 2.0)
100
+ assert_equal 2.0, @mockable.read
101
+ @mockable.write(3.0)
102
+ assert_equal 3.0, @mockable.read
103
+ @mockable.mock_set(:minimum => 1.0, :average => 2.0, :maximum => 3.0)
104
+ assert_equal 1.0, @mockable.minimum(0,100)
105
+ assert_equal 2.0, @mockable.average(0,100)
106
+ assert_equal 3.0, @mockable.maximum(0,100)
107
+ assert_equal 3.0, @mockable.read
108
+ end
109
+ end
@@ -0,0 +1,77 @@
1
+ require File.dirname(__FILE__)+'/test_helper.rb'
2
+
3
+ class TestSensors < Test::Unit::TestCase
4
+ def setup
5
+ @defs = YAML::load File.new(TEST_SENSORS_FILE)
6
+ @sensors = SAAL::Sensors.new(TEST_SENSORS_FILE, TEST_DBCONF)
7
+ end
8
+
9
+ def test_get_sensor
10
+ @defs.each do |name, value|
11
+ s = @sensors.send name
12
+ assert_equal s.description, value['name']
13
+ end
14
+ end
15
+
16
+ def test_read
17
+ with_fake_owserver do
18
+ assert_instance_of Float, @sensors.fake_temp.read
19
+ assert_nil @sensors.non_existant.read
20
+ assert_raise(NoMethodError) { @sensors.no_such_name.read }
21
+ end
22
+ end
23
+
24
+ def test_read_uncached
25
+ with_fake_owserver do
26
+ assert_instance_of Float, @sensors.fake_temp.read_uncached
27
+ assert_nil @sensors.non_existant.read_uncached
28
+ assert_raise(NoMethodError) { @sensors.no_such_name.read_uncached }
29
+ end
30
+ end
31
+
32
+ def test_each
33
+ expected = @defs.map{ |name, value| value['name']}
34
+ assert_equal expected, @sensors.map {|sensor| sensor.description}
35
+ end
36
+
37
+ def test_writeable
38
+ dinsensors = SAAL::Sensors.new(TEST_SENSORS_DINRELAY_FILE, TEST_DBCONF)
39
+ dinsensors.each {|s| assert s.writeable?}
40
+ owsensors = SAAL::Sensors.new(TEST_SENSORS_FILE, TEST_DBCONF)
41
+ owsensors.each {|s| assert !s.writeable?}
42
+ end
43
+
44
+ def test_average
45
+ db_setup
46
+
47
+ test_values = [[10, 7.323],[12, 5.432],[23, -2.125], [44, 0.123]]
48
+ test_average = (5.432 - 2.125)/2.0
49
+ test_values.each do |values|
50
+ @dbstore.write(:fake_temp, *values)
51
+ end
52
+
53
+ assert_instance_of Float, @sensors.fake_temp.average(11, 25)
54
+ assert_in_delta test_average, @sensors.fake_temp.average(11, 25), 0.001
55
+ assert_in_delta test_average, @sensors.fake_temp.average(12, 25), 0.001
56
+ assert_in_delta test_average, @sensors.fake_temp.average(11, 23), 0.001
57
+
58
+ # when there are no points it's nil
59
+ assert_nil @sensors.fake_temp.average(50, 60)
60
+ end
61
+
62
+ def test_store_value
63
+ db_setup
64
+
65
+ with_fake_owserver do
66
+ @sensors.fake_temp.store_value
67
+ end
68
+
69
+ db_test_query("SELECT * FROM sensor_reads") do |res|
70
+ assert_equal 1, res.num_rows
71
+ row = res.fetch_row
72
+ assert_equal "fake_temp", row[0]
73
+ assert_in_delta Time.now.utc.to_i, row[1], 100
74
+ assert_instance_of Float, row[2].to_f
75
+ end
76
+ end
77
+ end
data/test/test_db.yml ADDED
@@ -0,0 +1,4 @@
1
+ host: localhost
2
+ user: sensor_reads
3
+ pass: abcd
4
+ db: sensor_reads_test
@@ -0,0 +1,16 @@
1
+ group1:
2
+ dinrelay:
3
+ host: localhost
4
+ port: 33333
5
+ user: someuser
6
+ pass: somepass
7
+ outlets:
8
+ 1: name1
9
+ 2: name2
10
+ 3: name3
11
+ 4: name4
12
+ 5: name5
13
+ 6: name6
14
+ 7: name7
15
+ 8: name8
16
+
@@ -0,0 +1,37 @@
1
+ require 'test/unit'
2
+ require 'yaml'
3
+ require 'fileutils'
4
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
5
+
6
+ class Test::Unit::TestCase
7
+ TEST_SENSORS_FILE = File.dirname(__FILE__)+'/test_sensors.yml'
8
+ TEST_SENSORS_DINRELAY_FILE = File.dirname(__FILE__)+'/test_dinrelay_sensors.yml'
9
+ TEST_SENSOR_CLEANUPS_FILE = File.dirname(__FILE__)+'/test_sensor_cleanups.yml'
10
+ TEST_NONEXIST_SENSOR_FILE = File.dirname(__FILE__)+'/nonexistant_sensor.yml'
11
+ TEST_DBCONF = File.dirname(__FILE__)+'/test_db.yml'
12
+ TEST_DBOPTS = YAML::load(File.new(TEST_DBCONF))
13
+
14
+ def with_fake_owserver
15
+ pid = fork do
16
+ exec("owserver", "--fake", "1F,10", "--foreground")
17
+ end
18
+ sleep 1 # Potential timing bug when the system is under load
19
+ yield
20
+ Process.kill("KILL", pid)
21
+ Process.waitpid(pid)
22
+ end
23
+
24
+ def db_setup
25
+ @dbstore = SAAL::DBStore.new(TEST_DBCONF)
26
+ @dbstore.db_wipe
27
+ @dbstore.db_initialize
28
+ end
29
+
30
+ def db_test_query(query)
31
+ db = Mysql.new(TEST_DBOPTS['host'],TEST_DBOPTS['user'],
32
+ TEST_DBOPTS['pass'],TEST_DBOPTS['db'])
33
+ res = db.query(query)
34
+ yield res
35
+ db.close
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ fake:
2
+ name: "A fake temperature sensor"
3
+ max_value: 500
4
+ min_value: 100
5
+ onewire:
6
+ serial: /10.4AEC29CDBAAB/temperature
7
+
8
+ fake2:
9
+ name: "A fake temperature sensor"
10
+ max_value: 500
11
+ max_correctable: 550
12
+ min_value: 100
13
+ min_correctable: 50
14
+ onewire:
15
+ serial: /10.4AEC29CDBAAB/temperature
16
+
17
+ fake3:
18
+ name: "A fake temperature sensor"
19
+ onewire:
20
+ serial: /10.4AEC29CDBAAB/temperature
21
+
@@ -0,0 +1,10 @@
1
+ fake_temp:
2
+ name: "A fake temperature sensor"
3
+ onewire:
4
+ serial: /10.4AEC29CDBAAB/temperature
5
+
6
+ non_existant:
7
+ name: "A non-existant sensor"
8
+ onewire:
9
+ serial: /10.A00000000000/temperature
10
+
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: saal
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 2
10
+ version: 0.2.2
11
+ platform: ruby
12
+ authors:
13
+ - "Pedro C\xC3\xB4rte-Real"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-12-29 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ownet
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 27
30
+ segments:
31
+ - 0
32
+ - 1
33
+ - 0
34
+ version: 0.1.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: nokogiri
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: mysql
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ description: |
66
+ A daemon and libraries to create an abstraction layer that interfaces with
67
+ sensors and actuators, recording their state, responding to requests
68
+ for current and historical values, and allowing changes of state.
69
+
70
+ email: pedro@pedrocr.net
71
+ executables:
72
+ - saal_readall
73
+ - saal_dump_database
74
+ - dinrelayset
75
+ - dinrelaystatus
76
+ - saal_import_mysql
77
+ - saal_daemon
78
+ - saal_chart
79
+ extensions: []
80
+
81
+ extra_rdoc_files:
82
+ - README.rdoc
83
+ - LICENSE
84
+ files:
85
+ - LICENSE
86
+ - README.rdoc
87
+ - Rakefile
88
+ - TODO
89
+ - bin/.gitignore
90
+ - bin/dinrelayset
91
+ - bin/dinrelaystatus
92
+ - bin/saal_chart
93
+ - bin/saal_daemon
94
+ - bin/saal_dump_database
95
+ - bin/saal_import_mysql
96
+ - bin/saal_readall
97
+ - lib/chart_data.rb
98
+ - lib/daemon.rb
99
+ - lib/dbstore.rb
100
+ - lib/dinrelay.rb
101
+ - lib/outliercache.rb
102
+ - lib/owsensor.rb
103
+ - lib/saal.rb
104
+ - lib/sensor.rb
105
+ - lib/sensors.rb
106
+ - saal.gemspec
107
+ - test/chart_data_test.rb
108
+ - test/daemon_test.rb
109
+ - test/dbstore_test.rb
110
+ - test/dinrelay.html.erb
111
+ - test/dinrelay_test.rb
112
+ - test/nonexistant_sensor.yml
113
+ - test/outliercache_test.rb
114
+ - test/sensor_test.rb
115
+ - test/sensors_test.rb
116
+ - test/test_db.yml
117
+ - test/test_dinrelay_sensors.yml
118
+ - test/test_helper.rb
119
+ - test/test_sensor_cleanups.yml
120
+ - test/test_sensors.yml
121
+ has_rdoc: true
122
+ homepage: https://github.com/pedrocr/saal
123
+ licenses: []
124
+
125
+ post_install_message:
126
+ rdoc_options:
127
+ - -S
128
+ - -w 2
129
+ - -N
130
+ - -c utf8
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ hash: 3
139
+ segments:
140
+ - 0
141
+ version: "0"
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 3
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ requirements: []
152
+
153
+ rubyforge_project:
154
+ rubygems_version: 1.3.7
155
+ signing_key:
156
+ specification_version: 2
157
+ summary: Thin abstraction layer for interfacing and recording sensors (currently onewire) and actuators (currently dinrelay)
158
+ test_files:
159
+ - test/chart_data_test.rb
160
+ - test/daemon_test.rb
161
+ - test/dbstore_test.rb
162
+ - test/dinrelay_test.rb
163
+ - test/outliercache_test.rb
164
+ - test/sensor_test.rb
165
+ - test/sensors_test.rb
166
+ - test/test_helper.rb