saal 0.2.25 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/saal.gemspec CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
6
6
  s.platform = Gem::Platform::RUBY
7
7
 
8
8
  s.name = 'saal'
9
- s.version = '0.2.25'
10
- s.date = '2014-01-03'
9
+ s.version = '0.3.4'
10
+ s.date = '2021-04-04'
11
11
 
12
12
  s.summary = "Thin abstraction layer for interfacing and recording sensors (currently onewire) and actuators (currently dinrelay)"
13
13
  s.description = <<EOF
@@ -19,6 +19,7 @@ EOF
19
19
  s.authors = ["Pedro Côrte-Real"]
20
20
  s.email = 'pedro@pedrocr.net'
21
21
  s.homepage = 'https://github.com/pedrocr/saal'
22
+ s.licenses = 'LGPL-2.1'
22
23
 
23
24
  s.require_paths = %w[lib]
24
25
 
@@ -28,9 +29,10 @@ EOF
28
29
 
29
30
  s.executables = Dir.glob("bin/*").map{|f| f.gsub('bin/','')}
30
31
 
31
- s.add_dependency('ownet', [">= 0.2.1"])
32
- s.add_dependency('nokogiri')
33
- s.add_dependency('mysql')
32
+ s.add_runtime_dependency 'ownet', "~>0.2"
33
+ s.add_runtime_dependency 'mysql2', "~>0.5"
34
+ s.add_runtime_dependency 'nokogiri', '~>1.8'
35
+ s.add_runtime_dependency 'net-http-digest_auth', '~>1.4'
34
36
 
35
37
  # = MANIFEST =
36
38
  s.files = %w[
@@ -43,7 +45,10 @@ EOF
43
45
  bin/dinrelaystatus
44
46
  bin/saal_chart
45
47
  bin/saal_daemon
48
+ bin/saal_denkovi_relays
46
49
  bin/saal_dump_database
50
+ bin/saal_envoy_generate_config
51
+ bin/saal_envoy_read
47
52
  bin/saal_import_mysql
48
53
  bin/saal_readall
49
54
  lib/chart.rb
@@ -51,7 +56,10 @@ EOF
51
56
  lib/charts.rb
52
57
  lib/daemon.rb
53
58
  lib/dbstore.rb
59
+ lib/denkovi.rb
54
60
  lib/dinrelay.rb
61
+ lib/envoy.rb
62
+ lib/http.rb
55
63
  lib/outliercache.rb
56
64
  lib/owsensor.rb
57
65
  lib/saal.rb
@@ -63,6 +71,8 @@ EOF
63
71
  test/charts_test.rb
64
72
  test/daemon_test.rb
65
73
  test/dbstore_test.rb
74
+ test/denkovi.json.erb
75
+ test/denkovi_test.rb
66
76
  test/dinrelay.html.erb
67
77
  test/dinrelay_test.rb
68
78
  test/nonexistant_sensor.yml
@@ -71,6 +81,7 @@ EOF
71
81
  test/sensors_test.rb
72
82
  test/test_charts.yml
73
83
  test/test_db.yml
84
+ test/test_denkovi_sensors.yml
74
85
  test/test_dinrelay_sensors.yml
75
86
  test/test_helper.rb
76
87
  test/test_sensor_cleanups.yml
@@ -78,7 +78,7 @@ class TestChartData < Test::Unit::TestCase
78
78
  Time.utc(2012, 3, 25, 23, 59, 59),
79
79
  ["0"]+(2..23).map{|s| s.to_s}+["0"],
80
80
  Time.utc(2012, 3, 25, 23, 59, 59),
81
- "_changing_timezone_test","Europe/Lisbon")
81
+ "_changing_timezone_test2","Europe/Lisbon")
82
82
 
83
83
  assert_alignment_interval(24, :hours, Time.utc(2010, 12, 29, 16, 0, 0),
84
84
  Time.utc(2010, 12, 30, 15, 59, 59),
data/test/chart_test.rb CHANGED
@@ -16,7 +16,6 @@ class TestChart < Test::Unit::TestCase
16
16
 
17
17
  def test_average
18
18
  name = 'week'
19
- defs = @defs[name]
20
19
  chart = @charts.find(name)
21
20
  assert_equal ['Fri','Sat','Sun','Mon','Tue','Wed','Thu'], chart.periodnames
22
21
  chart.sensors.each {|s| s.mock_set(:average => 1)}
@@ -31,7 +30,6 @@ class TestChart < Test::Unit::TestCase
31
30
 
32
31
  def test_min_max_avg_1arity
33
32
  name = 'week'
34
- defs = @defs[name]
35
33
  chart = @charts.find(name)
36
34
  assert_equal ['Fri','Sat','Sun','Mon','Tue','Wed','Thu'], chart.periodnames
37
35
  v = {:minimum => 1.0, :maximum => 2.0, :average => 1.5}
@@ -43,7 +41,6 @@ class TestChart < Test::Unit::TestCase
43
41
 
44
42
  def test_min_max_0arity
45
43
  name = 'week'
46
- defs = @defs[name]
47
44
  chart = @charts.find(name)
48
45
  assert_equal ['Fri','Sat','Sun','Mon','Tue','Wed','Thu'], chart.periodnames
49
46
  v = {:minimum => 1.0, :maximum => 2.0, :average => 1.5}
data/test/daemon_test.rb CHANGED
@@ -18,7 +18,7 @@ class TestDaemon < Test::Unit::TestCase
18
18
  end
19
19
 
20
20
  db_test_query("SELECT * FROM sensor_reads") do |res|
21
- assert res.num_rows > 0, "No sensor reads in DB"
21
+ assert res.count > 0, "No sensor reads in DB"
22
22
  end
23
23
  end
24
24
 
@@ -38,7 +38,7 @@ class TestDaemon < Test::Unit::TestCase
38
38
  end
39
39
 
40
40
  db_test_query("SELECT * FROM sensor_reads") do |res|
41
- assert res.num_rows == 0
41
+ assert res.count == 0
42
42
  end
43
43
  end
44
44
  end
data/test/dbstore_test.rb CHANGED
@@ -9,8 +9,11 @@ class TestFileStore < Test::Unit::TestCase
9
9
  @dbstore.write(:test_sensor, test_time, test_value)
10
10
 
11
11
  db_test_query("SELECT * FROM sensor_reads") do |res|
12
- assert_equal 1, res.num_rows
13
- assert_equal ["test_sensor", test_time.to_s, test_value.to_s], res.fetch_row
12
+ assert_equal 1, res.count
13
+ row = res.first
14
+ assert_equal "test_sensor", row["sensor"]
15
+ assert_equal test_time, row["date"].to_i
16
+ assert_equal test_value, row["value"].to_f
14
17
  end
15
18
  end
16
19
 
@@ -39,6 +42,21 @@ class TestFileStore < Test::Unit::TestCase
39
42
  assert_nil @dbstore.average(:test_sensor, 50, 60)
40
43
  end
41
44
 
45
+ def test_weighted_average
46
+ db_setup
47
+ test_values = [[10, 7.323],[12, 5.432],[23, -2.125], [44, 0.123]]
48
+ test_average = ((12-10)*7.323+(23-12)*5.432+(44-23)*(-2.125)) / (44-10)
49
+ test_values.each do |values|
50
+ @dbstore.write(:test_sensor, *values)
51
+ end
52
+
53
+ assert_instance_of Float, @dbstore.average(:test_sensor, 10, 44)
54
+ assert_in_delta test_average, @dbstore.weighted_average(:test_sensor, 10, 44), 0.001
55
+
56
+ # when there are no points it's nil
57
+ assert_nil @dbstore.weighted_average(:test_sensor, 50, 60)
58
+ end
59
+
42
60
  def test_min_max
43
61
  db_setup
44
62
  test_values = [[10, 7.323],[12, 5.432],[23, -2.125], [44, 0.123]]
@@ -0,0 +1,39 @@
1
+ {
2
+
3
+ "CurrentState": {
4
+
5
+ "Output": [
6
+
7
+ <% (1..16).each do |num| %>
8
+ <% state = outlets[num] %>
9
+ <% val = state == "ON" ? "1" : "0" %>
10
+ <% if num < 16 %>
11
+ {"Name": "RELAY<%= num %>", "Value": "<%= val %>"},
12
+ <% else %>
13
+ {"Name": "RELAY<%= num %>", "Value": "<%= val %>"}
14
+ <% end %>
15
+ <% end %>
16
+
17
+ ],
18
+
19
+ "Auto-reboot": {
20
+
21
+ "RebootsNumber": "0",
22
+
23
+ "LastReboot": "-"
24
+
25
+ },
26
+
27
+ "Device": {
28
+
29
+ "Name": "SMARTDEN_IP-16R",
30
+
31
+ "MAC": "E8:EA:DA:AA:AA:AA"
32
+
33
+ }
34
+
35
+ }
36
+
37
+ }
38
+
39
+
@@ -0,0 +1,212 @@
1
+ require File.dirname(__FILE__)+'/test_helper.rb'
2
+ require 'webrick'
3
+ require 'benchmark'
4
+
5
+ class TestDenkoviRelay < Test::Unit::TestCase
6
+ def setup
7
+ @@serv_num ||= 0
8
+ @@serv_num += 1
9
+
10
+ @og=SAAL::Denkovi::OutletGroup.new(service_opts)
11
+ @vals={1=>"OFF",2=>"OFF",3=>"ON",4=>"OFF",5=>"ON",6=>"ON",7=>"ON",8=>"OFF",
12
+ 9=>"OFF",10=>"OFF",11=>"ON",12=>"OFF",13=>"ON",14=>"ON",15=>"ON",16=>"OFF"}
13
+ @rvals={1=>"ON",2=>"ON",3=>"OFF",4=>"ON",5=>"OFF",6=>"OFF",7=>"OFF",8=>"ON",
14
+ 9=>"ON",10=>"ON",11=>"OFF",12=>"ON",13=>"OFF",14=>"OFF",15=>"OFF",16=>"ON"}
15
+
16
+ defs = YAML::load(File.new(TEST_SENSORS_DENKOVI_FILE))
17
+ @pass = defs["group1"]["denkovi"]["pass"]
18
+ defs['group1']['denkovi']['port'] = service_opts[:port]
19
+ tempfile = Tempfile.new('denkovi_test_yml')
20
+ File.open(tempfile.path,'w') {|f| f.write(YAML::dump defs)}
21
+ @test_sensors_denkovi_file = tempfile.path
22
+ end
23
+
24
+ def service_opts
25
+ base_opts = {:host => 'localhost', :pass =>"somepass"}
26
+ base_opts.merge(:port => 3333+@@serv_num)
27
+ end
28
+
29
+ class BasicServing < WEBrick::HTTPServlet::AbstractServlet
30
+ def self.get_instance(config, opts)
31
+ new(opts)
32
+ end
33
+ def initialize(opts)
34
+ @html = opts[:html]
35
+ @pass = opts[:pass]
36
+ @status = opts[:status] || 200
37
+ @feedback = opts[:feedback] || {}
38
+ @sleep = opts[:sleep] || 0
39
+ end
40
+ def do_GET(req, res)
41
+ sleep @sleep
42
+ @feedback[:uris] ||= []
43
+ @feedback[:uris] << req.request_uri.to_s
44
+ @feedback[:uri] = req.request_uri.to_s
45
+ @feedback[:nrequests] = (@feedback[:nrequests]||0)+1
46
+ res.body = @html
47
+ res.status = @status
48
+ res['Content-Type'] = "text/html"
49
+ end
50
+ end
51
+
52
+ def with_webrick(opts)
53
+ opts = service_opts.merge(opts)
54
+
55
+ Socket.do_not_reverse_lookup = true # Speed up startup
56
+ log = WEBrick::Log.new($stderr, WEBrick::Log::ERROR)
57
+ access_log = [[log, WEBrick::AccessLog::COMBINED_LOG_FORMAT]]
58
+ s = WEBrick::HTTPServer.new(:Port => opts[:port],
59
+ :Logger => log,
60
+ :AccessLog => access_log)
61
+ s.mount('/', BasicServing, opts.merge(:feedback => (f = {})))
62
+
63
+ thread = Thread.new do
64
+ s.start
65
+ end
66
+ while s.status != :Running; sleep 0.1; end # Make sure the server is up
67
+ yield f
68
+ s.shutdown
69
+ thread.exit
70
+ end
71
+
72
+ def create_current_state_json(hash)
73
+ outlets = hash
74
+ erb = ERB.new(File.open(File.dirname(__FILE__)+'/denkovi.json.erb').read)
75
+ erb.result(binding)
76
+ end
77
+
78
+ def assert_path(path, uri)
79
+ assert_equal "http://localhost:#{service_opts[:port]}"+path, uri
80
+ end
81
+
82
+ def test_read_state
83
+ with_webrick(:html=>create_current_state_json(@vals)) do |feedback|
84
+ @vals.each do |num, state|
85
+ assert_equal state, @og.state(num)
86
+ assert_path "/current_state.json?pw=#{@pass}", feedback[:uri]
87
+ end
88
+ end
89
+ end
90
+
91
+ def test_set_state
92
+ with_webrick(:html=>create_current_state_json(@rvals)) do |feedback|
93
+ @vals.each do |num, state|
94
+ newstate = state == "ON" ? "OFF" : "ON"
95
+ val = {"ON" => "1", "OFF" => "0"}[newstate]
96
+ assert @og.set_state(num,newstate), "State change not working"
97
+ assert_path "/current_state.json?pw=#{@pass}&Relay#{num}=#{val}", feedback[:uri]
98
+ end
99
+ end
100
+ end
101
+
102
+ def test_enumerate_sensors
103
+ sensors = SAAL::Sensors.new(@test_sensors_denkovi_file, TEST_DBCONF)
104
+ assert_equal((1..16).map{|i| "name#{i.to_s.rjust(2, "0")}"}, sensors.map{|s| s.name}.sort)
105
+ assert_equal((1..16).map{|i| "description#{i.to_s.rjust(2, "0")}"}, sensors.map{|s| s.description}.sort)
106
+ end
107
+
108
+ def test_sensor_type
109
+ SAAL::Sensors.new(TEST_SENSORS_DENKOVI_FILE, TEST_DBCONF).each do |s|
110
+ assert_equal :onoff, s.sensor_type
111
+ end
112
+ end
113
+
114
+ def test_read_sensors
115
+ sensors = SAAL::Sensors.new(@test_sensors_denkovi_file, TEST_DBCONF)
116
+ with_webrick(:html=>create_current_state_json(@vals)) do |feedback|
117
+ @vals.each do |num, state|
118
+ value = state == "ON" ? 1.0 : 0.0
119
+ assert_equal value, sensors.send('name'+num.to_s.rjust(2, "0")).read
120
+ assert_path "/current_state.json?pw=#{@pass}", feedback[:uri]
121
+ end
122
+ assert_equal 1, feedback[:nrequests], "denkovi request caching not working"
123
+ end
124
+ end
125
+
126
+ def test_set_sensors
127
+ sensors = SAAL::Sensors.new(@test_sensors_denkovi_file, TEST_DBCONF)
128
+ with_webrick(:html=>create_current_state_json(@rvals)) do |feedback|
129
+ @vals.each do |num, state|
130
+ newval = state == "ON" ? 0.0 : 1.0
131
+ newstate = state == "ON" ? "OFF" : "ON"
132
+ assert_equal newval, sensors.send('name'+num.to_s.rjust(2, "0")).write(newval),
133
+ "State change not working"
134
+ val = {"ON" => "1", "OFF" => "0"}[newstate]
135
+ assert_path "/current_state.json?pw=#{@pass}&Relay#{num}=#{val}", feedback[:uris][-2]
136
+ assert_path "/current_state.json?pw=#{@pass}", feedback[:uris][-1]
137
+ end
138
+ end
139
+ end
140
+
141
+ # Test that write invalidates any caching
142
+ def test_write_read_sensors
143
+ sensors = SAAL::Sensors.new(@test_sensors_denkovi_file, TEST_DBCONF)
144
+ with_webrick(:html=>create_current_state_json(@vals)) do |feedback|
145
+ @vals.each do |num, state|
146
+ sensors.send('name'+num.to_s.rjust(2, "0")).write(0.0)
147
+ sensors.send('name'+num.to_s.rjust(2, "0")).read
148
+ end
149
+ assert_equal 32, feedback[:nrequests], "denkovi caching too much"
150
+ end
151
+ end
152
+
153
+ # Test that the cache times out
154
+ def test_cache_invalidation
155
+ _sensors = SAAL::Sensors.new(@test_sensors_denkovi_file, TEST_DBCONF)
156
+ @og.cache_timeout = 0.1
157
+ with_webrick(:html=>create_current_state_json(@vals)) do |feedback|
158
+ @og.state(1)
159
+ sleep 0.2
160
+ @og.state(1)
161
+ assert_equal 2, feedback[:nrequests], "denkovi caching not invalidating"
162
+ end
163
+ end
164
+
165
+ def test_failed_connection
166
+ @vals.each do |num, state|
167
+ assert_equal nil, @og.state(num)
168
+ assert !@og.set_state(num,"ON"), "State change working without a server?!"
169
+ end
170
+ end
171
+
172
+ def test_failed_request
173
+ with_webrick(:html=>create_current_state_json(@vals),:status=>404) do |feedback|
174
+ @vals.each do |num, state|
175
+ assert_equal nil, @og.state(num)
176
+ assert !@og.set_state(num,"ON"), "State change working without a server?!"
177
+ end
178
+ end
179
+ end
180
+
181
+ def test_fast_open_timeout
182
+ #FIXME: Find a way to make this test address more generic
183
+ @og=SAAL::Denkovi::OutletGroup.new(service_opts.merge(:host => "10.254.254.254",
184
+ :timeout=>0.1))
185
+ with_webrick(:html=>create_current_state_json(@vals)) do |feedback|
186
+ time = Benchmark.measure do
187
+ @vals.each do |num, state|
188
+ assert_equal nil, @og.state(num), "Read not timing out?"
189
+ assert !@og.set_state(num,"ON"), "State change not timing out?"
190
+ end
191
+ end
192
+ total_time = @og.timeout*2*@vals.keys.size
193
+ assert time.total < total_time
194
+ "Doing the reads took too long, are we really timing out?"
195
+ end
196
+ end
197
+
198
+ def test_fast_read_timeout
199
+ @og=SAAL::Denkovi::OutletGroup.new(service_opts.merge(:timeout=>0.1))
200
+ with_webrick(:html=>create_current_state_json(@vals),:sleep=>10) do |feedback|
201
+ time = Benchmark.measure do
202
+ @vals.each do |num, state|
203
+ assert_equal nil, @og.state(num), "Read not timing out?"
204
+ assert !@og.set_state(num,"ON"), "State change not timing out?"
205
+ end
206
+ end
207
+ total_time = @og.timeout*2*@vals.keys.size
208
+ assert time.total < total_time
209
+ "Doing the reads took too long, are we really timing out?"
210
+ end
211
+ end
212
+ end
@@ -151,7 +151,7 @@ class TestDINRelay < Test::Unit::TestCase
151
151
 
152
152
  # Test that the cache times out
153
153
  def test_cache_invalidation
154
- sensors = SAAL::Sensors.new(@test_sensors_dinrelay_file, TEST_DBCONF)
154
+ _sensors = SAAL::Sensors.new(@test_sensors_dinrelay_file, TEST_DBCONF)
155
155
  @og.cache_timeout = 0.1
156
156
  with_webrick(:html=>create_index_html(@vals)) do |feedback|
157
157
  @og.state(1)
data/test/sensor_test.rb CHANGED
@@ -31,6 +31,9 @@ class MockDBStore
31
31
  def average(sensor, from, to)
32
32
  @value
33
33
  end
34
+ def weighted_average(sensor, from, to)
35
+ @value
36
+ end
34
37
  def minimum(sensor, from, to)
35
38
  @value
36
39
  end
@@ -112,6 +115,7 @@ class TestSensor < Test::Unit::TestCase
112
115
  assert_equal corrected, sensor.minimum(0,100)
113
116
  assert_equal corrected, sensor.maximum(0,100)
114
117
  assert_equal corrected, sensor.average(0,100)
118
+ assert_equal corrected, sensor.weighted_average(0,100)
115
119
  end
116
120
 
117
121
  def test_linear_correction
@@ -125,6 +129,7 @@ class TestSensor < Test::Unit::TestCase
125
129
  assert_equal corrected, sensor.minimum(0,100)
126
130
  assert_equal corrected, sensor.maximum(0,100)
127
131
  assert_equal corrected, sensor.average(0,100)
132
+ assert_equal corrected, sensor.weighted_average(0,100)
128
133
  end
129
134
 
130
135
  def test_sensor_type
@@ -145,9 +150,10 @@ class TestSensor < Test::Unit::TestCase
145
150
  assert_equal 2.0, @mockable.read
146
151
  @mockable.write(3.0)
147
152
  assert_equal 3.0, @mockable.read
148
- @mockable.mock_set(:minimum => 1.0, :average => 2.0, :maximum => 3.0, :last_value => 5.0)
153
+ @mockable.mock_set(:minimum => 1.0, :average => 2.0, :weighted_average => 2.5, :maximum => 3.0, :last_value => 5.0)
149
154
  assert_equal 1.0, @mockable.minimum(0,100)
150
155
  assert_equal 2.0, @mockable.average(0,100)
156
+ assert_equal 2.5, @mockable.weighted_average(0,100)
151
157
  assert_equal 3.0, @mockable.maximum(0,100)
152
158
  assert_equal 5.0, @mockable.last_value
153
159
  assert_equal 3.0, @mockable.read