saal 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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