saal 0.2.25 → 0.3.4

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.
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