saal 0.2.2 → 0.2.11
Sign up to get free protection for your applications and to get access to all the features.
- data/TODO +7 -1
- data/bin/saal_chart +72 -98
- data/bin/saal_daemon +14 -6
- data/lib/chart.rb +50 -0
- data/lib/chart_data.rb +106 -22
- data/lib/charts.rb +23 -0
- data/lib/daemon.rb +11 -6
- data/lib/dbstore.rb +2 -2
- data/lib/dinrelay.rb +19 -10
- data/lib/outliercache.rb +1 -1
- data/lib/owsensor.rb +4 -1
- data/lib/saal.rb +4 -1
- data/lib/sensor.rb +22 -7
- data/lib/sensors.rb +3 -2
- data/saal.gemspec +7 -2
- data/test/chart_data_test.rb +106 -13
- data/test/chart_test.rb +55 -0
- data/test/charts_test.rb +31 -0
- data/test/dinrelay_test.rb +26 -1
- data/test/sensor_test.rb +37 -1
- data/test/test_charts.yml +27 -0
- data/test/test_dinrelay_sensors.yml +9 -1
- data/test/test_helper.rb +1 -0
- data/test/test_sensor_cleanups.yml +16 -0
- metadata +11 -4
data/TODO
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
TODO
|
2
|
+
!-Index the value column of the sensor reads for minimum and maximum
|
3
|
+
- Change the filtering operations (e.g., outliercache) so that the raw value is always stored in the database
|
4
|
+
- Make the outliercache filter based on the expected sensor range (e.g. -20-50 in temperature and 800-1200 in pressure) so as to not be overly sensitive when around 0)
|
2
5
|
- Add logging to the daemon
|
3
6
|
?- Change the sensor configuration to be a ruby DSL and make it a daemon config
|
4
7
|
- Split classes into one per file with corresponding test (rails style)
|
5
8
|
- Verify inputs on the server to make sure it never crashes
|
6
9
|
- Remove the chart configuration options from the saal_chart script
|
10
|
+
?- Remove Sensors and Charts and move their functionality to Sensor and Chart
|
7
11
|
- Add an init.d file to the package (and possibly an installer script for ubuntu/debian)
|
8
|
-
- Add
|
12
|
+
- Add interface that does retries for reading as well as writing (e.g., dinrelay confirm state change)
|
9
13
|
DONE
|
10
14
|
X- Make the server bind only to a certain interface (not applicable)
|
11
15
|
X- Override OWNet::Connection with a mock object so that owserver is not needed
|
@@ -22,3 +26,5 @@ X- Make connections persistant
|
|
22
26
|
- Make the date returned by GET the date of the last read (GET doesn't return
|
23
27
|
a date now
|
24
28
|
- Implement monthly, yearly and 10-day average charts
|
29
|
+
- Add outlier detection and removal
|
30
|
+
- Add filter support to sensor reads (e.g., altitude compensation for pressure)
|
data/bin/saal_chart
CHANGED
@@ -1,110 +1,84 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
NUM_VALUES = 500 # Number of datapoints per series in the chart
|
2
3
|
|
3
4
|
require File.dirname(__FILE__)+'/../lib/saal.rb'
|
4
5
|
|
5
6
|
def usage
|
6
|
-
$stderr.puts("Usage: saal_chart <
|
7
|
+
$stderr.puts("Usage: saal_chart <chart dir>")
|
7
8
|
end
|
8
9
|
|
9
|
-
if ARGV.size !=
|
10
|
+
if ARGV.size != 1
|
10
11
|
usage
|
11
12
|
exit (2)
|
12
13
|
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@
|
76
|
-
|
77
|
-
|
78
|
-
@
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
@dataurl = @data.map {|values| values.join(",")}.join('|')
|
85
|
-
|
86
|
-
r = {}
|
87
|
-
case ALIGNNAMES
|
88
|
-
when :center
|
89
|
-
@periodnamesurl = "||"+PERIODNAMES.join('||')+"||"
|
90
|
-
when :left
|
91
|
-
@periodnamesurl = "|"+PERIODNAMES.join('|')+"||"
|
92
|
-
r[:chxs] = "0,555555,11,-1,lt"
|
15
|
+
SENSORS = [[:temp_exterior, [-15, 45]], [:temp_estufa, [-15, 45]],
|
16
|
+
[:hum_exterior, [0,100]],
|
17
|
+
[:pressao, [950,1050]]]
|
18
|
+
|
19
|
+
|
20
|
+
SAAL::Charts.new.each do |chart|
|
21
|
+
$stderr.puts "Generating chart #{chart.name}"
|
22
|
+
|
23
|
+
pngfile = ARGV[0]+'/chart-'+chart.name.to_s+'.png'
|
24
|
+
ymlfile = ARGV[0]+'/chart-'+chart.name.to_s+'.yml'
|
25
|
+
|
26
|
+
@mins = chart.minimum
|
27
|
+
@maxs = chart.maximum
|
28
|
+
@avgs = chart.average
|
29
|
+
@minmax = {}
|
30
|
+
SENSORS.each do |s,r|
|
31
|
+
@minmax[s] = {:maximum => @maxs[s], :minimum => @mins[s], :average => @avgs[s]}
|
32
|
+
end
|
33
|
+
|
34
|
+
File.open(ymlfile, 'w').write(YAML::dump(@minmax))
|
35
|
+
|
36
|
+
def normalize_data(data, min, max, offset=0)
|
37
|
+
data.map do |i|
|
38
|
+
if i.nil?
|
39
|
+
-1.0
|
40
|
+
elsif i < min
|
41
|
+
0.0
|
42
|
+
elsif i > max
|
43
|
+
100.0
|
44
|
+
else
|
45
|
+
(((i-min)/(max-min).to_f)*1000).round/10.0+offset
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@periodnames = chart.periodnames
|
51
|
+
@numperiods = @periodnames.size
|
52
|
+
@averages = chart.average(NUM_VALUES)
|
53
|
+
|
54
|
+
@data = SENSORS.map do |sensor, range|
|
55
|
+
normalize_data(@averages[sensor], *range)
|
56
|
+
end
|
57
|
+
|
58
|
+
@dataurl = @data.map {|values| values.join(",")}.join('|')
|
59
|
+
|
60
|
+
r = {}
|
61
|
+
case chart.alignlabels
|
62
|
+
when :center
|
63
|
+
@periodnamesurl = "||"+@periodnames.join('||')+"||"
|
64
|
+
when :left
|
65
|
+
@periodnamesurl = "|"+@periodnames.join('|')+"||"
|
66
|
+
r[:chxs] = "0,555555,11,-1,lt"
|
67
|
+
end
|
68
|
+
@xincr = 100.0/@numperiods.to_f*10000.truncate.to_f/10000
|
69
|
+
|
70
|
+
r[:chof] = "png"
|
71
|
+
r[:chs] = "700x300"
|
72
|
+
r[:cht] = "lc"
|
73
|
+
r[:chco] = "00ff00,ff0000,0000ff,ffff00"
|
74
|
+
r[:chxt] = "x,y,y,r"
|
75
|
+
r[:chxl] = "0:#{@periodnamesurl}1:|-15ºC||0||15||30||45ºC|2:|0%|25|50|75|100%|3:|950||975||1000||1025||1050 hPa"
|
76
|
+
r[:chg] = "#{@xincr},12.5,1,5"
|
77
|
+
r[:chd] = "t:#{@dataurl}"
|
78
|
+
|
79
|
+
@url = "http://chart.apis.google.com/chart?&"
|
80
|
+
@postdata = r.map{|k,v| k.to_s+"="+v}.join("&")
|
81
|
+
|
82
|
+
|
83
|
+
system "wget --quiet \"#{@url}\" --post-data=\"#{@postdata}\" -O #{pngfile}"
|
93
84
|
end
|
94
|
-
@xincr = 100.0/NUMPERIODS.to_f*10000.truncate.to_f/10000
|
95
|
-
|
96
|
-
r[:chof] = "png"
|
97
|
-
r[:chs] = "700x300"
|
98
|
-
r[:cht] = "lc"
|
99
|
-
r[:chco] = "00ff00,ff0000,0000ff,ffff00"
|
100
|
-
r[:chxt] = "x,y,y,r"
|
101
|
-
r[:chxl] = "0:#{@periodnamesurl}1:|-15ºC||0||15||30||45ºC|2:|0%|25|50|75|100%|3:|950||975||1000||1025||1050 hPa"
|
102
|
-
r[:chg] = "#{@xincr},12.5,1,5"
|
103
|
-
r[:chd] = "t:#{@dataurl}"
|
104
|
-
|
105
|
-
@url = "http://chart.apis.google.com/chart?&"
|
106
|
-
@postdata = r.map{|k,v| k.to_s+"="+v}.join("&")
|
107
|
-
|
108
|
-
|
109
|
-
system "wget --quiet \"#{@url}\" --post-data=\"#{@postdata}\" -O #{ARGV[1]}"
|
110
|
-
|
data/bin/saal_daemon
CHANGED
@@ -6,17 +6,25 @@ SENSORSCONF = "/etc/saal/sensors.yml"
|
|
6
6
|
|
7
7
|
require File.dirname(__FILE__)+'/../lib/saal.rb'
|
8
8
|
|
9
|
+
def usage
|
10
|
+
$stderr.puts "Usage: saal_daemon <pidfile|--foreground>"
|
11
|
+
end
|
12
|
+
|
9
13
|
if ARGV.size != 1
|
10
|
-
|
14
|
+
usage
|
11
15
|
exit 2
|
12
16
|
else
|
13
|
-
pidfile = ARGV[0]
|
17
|
+
pidfile = ARGV[0]
|
18
|
+
foreground = (ARGV[0] == '--foreground')
|
14
19
|
d = SAAL::Daemon.new(:interval => SENSOR_INTERVAL,
|
15
20
|
:sensorconf => SENSORSCONF,
|
16
|
-
:dbconf => DBCONF
|
21
|
+
:dbconf => DBCONF,
|
22
|
+
:foreground => foreground)
|
17
23
|
pid = d.run
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
if !foreground
|
25
|
+
File.open(pidfile, 'w') do |f|
|
26
|
+
f.write(pid)
|
27
|
+
f.close
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
data/lib/chart.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module SAAL
|
2
|
+
class Chart
|
3
|
+
attr_reader :name, :num, :periods, :alt, :description, :sensors, :alignlabels
|
4
|
+
def initialize(name, defs, sensors, opts={})
|
5
|
+
@name = name
|
6
|
+
@defs = defs
|
7
|
+
@alignlabels = (defs['alignlabels'] || :center).to_sym
|
8
|
+
@sensors = defs['sensors'].map{|name| sensors.send(name)}
|
9
|
+
@num = defs['last']
|
10
|
+
@periods = defs['periods']
|
11
|
+
@alt = defs['alt']
|
12
|
+
@description = defs['description']
|
13
|
+
@datarange = ChartDataRange.new(defs.merge(:now => opts[:now]))
|
14
|
+
end
|
15
|
+
|
16
|
+
def periodnames
|
17
|
+
@datarange.periodnames
|
18
|
+
end
|
19
|
+
|
20
|
+
def average(num=nil)
|
21
|
+
get_data(:average, num)
|
22
|
+
end
|
23
|
+
|
24
|
+
def minimum(num=nil)
|
25
|
+
get_data(:minimum, num)
|
26
|
+
end
|
27
|
+
|
28
|
+
def maximum(num=nil)
|
29
|
+
get_data(:maximum, num)
|
30
|
+
end
|
31
|
+
|
32
|
+
def from
|
33
|
+
@datarange.from
|
34
|
+
end
|
35
|
+
def to
|
36
|
+
@datarange.to
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def get_data(method, num)
|
41
|
+
n = num || 1
|
42
|
+
h = {}
|
43
|
+
@sensors.each do |s|
|
44
|
+
data = @datarange.get_data(method,s,n)
|
45
|
+
h[s.name.to_sym] = num ? data : data[0]
|
46
|
+
end
|
47
|
+
h
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/chart_data.rb
CHANGED
@@ -1,30 +1,114 @@
|
|
1
1
|
module SAAL
|
2
|
-
class
|
3
|
-
|
4
|
-
|
2
|
+
class ChartDataRange
|
3
|
+
ALIGN = {:years => [12,31,23,59,59],
|
4
|
+
:months => [31,23,59,59],
|
5
|
+
:days => [23,59,59],
|
6
|
+
:weeks => [23,59,59],
|
7
|
+
:hours => [59,59]}
|
8
|
+
|
9
|
+
NUMHOURS = {:hours => 1, :days => 24, :weeks => 24*7}
|
10
|
+
DAYNAMES = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]
|
11
|
+
MONTHNAMES = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
|
12
|
+
|
13
|
+
attr_reader :num, :periods
|
14
|
+
def initialize(opts={})
|
15
|
+
last = opts[:last] || opts['last'].to_i
|
16
|
+
periods = opts[:periods] || (opts['periods'] ? opts['periods'].to_sym : nil)
|
17
|
+
@now = opts[:now] || Time.now.utc
|
18
|
+
if last && periods
|
19
|
+
@num = last
|
20
|
+
@periods = periods
|
21
|
+
calc_alignment
|
22
|
+
else
|
23
|
+
@from = opts[:from] || 0
|
24
|
+
@to = opts[:to] || @now
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def from
|
29
|
+
@from.to_i
|
5
30
|
end
|
6
|
-
|
7
|
-
def
|
8
|
-
|
31
|
+
|
32
|
+
def to
|
33
|
+
@to.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_data(method, sensor, num)
|
37
|
+
step = (@to - @from).to_i/num
|
38
|
+
t = @from - 1
|
9
39
|
(0..num-2).map do |i|
|
10
|
-
f =
|
11
|
-
t = (
|
12
|
-
|
13
|
-
end <<
|
40
|
+
f = t + 1
|
41
|
+
t = (f+step)
|
42
|
+
v = sensor.send(method, f.to_i, t.to_i)
|
43
|
+
end << sensor.send(method, (t+1).to_i, to.to_i)
|
44
|
+
end
|
45
|
+
|
46
|
+
def periodnames
|
47
|
+
if !@num
|
48
|
+
raise RuntimeError,
|
49
|
+
"Trying to get periodnames without a :last & :periods definition"
|
50
|
+
end
|
51
|
+
|
52
|
+
case @periods
|
53
|
+
when :hours
|
54
|
+
(0...@num).map{|i| ((@now.hour - i)%24).to_s}.reverse
|
55
|
+
when :days
|
56
|
+
(1..@num).map{|i| (@now.wday - i)%7}.map{|w| DAYNAMES[w]}.reverse
|
57
|
+
when :weeks
|
58
|
+
initial = @now - (@now.wday-1)*24*60*60
|
59
|
+
(0...@num).map do |i|
|
60
|
+
time = Time.at(initial - i*24*60*60*7)
|
61
|
+
time.day.to_s+" "+ MONTHNAMES[time.month-1]
|
62
|
+
end.reverse
|
63
|
+
when :months
|
64
|
+
(1..@num).map{|i| (@now.month - i)%12}.map{|m| MONTHNAMES[m]}.reverse
|
65
|
+
when :years
|
66
|
+
(0...@num).map{|i| (@now.year - i).to_s}.reverse
|
67
|
+
else
|
68
|
+
raise RuntimeError, "No such period type #{@periods}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def calc_alignment
|
74
|
+
if [:years, :year].include? periods
|
75
|
+
# Calculate by date manipulation
|
76
|
+
from = Time.utc(@now.year - num + 1, 1, 1, 0, 0, 0)
|
77
|
+
to = Time.utc(@now.year, 12, 31, 23, 59, 59)
|
78
|
+
elsif [:months, :month].include? periods
|
79
|
+
# advance to the 1st of the next month
|
80
|
+
newm = @now.month%12 + 1
|
81
|
+
newy = @now.year + (@now.month == 12 ? 1 : 0)
|
82
|
+
to = Time.utc(newy, newm, 1, 0, 0, 0)
|
83
|
+
# Go back num months for from
|
84
|
+
from = dec_months(num, to)
|
85
|
+
# subtract 1 second from two to get the end of current month
|
86
|
+
to -= 1
|
87
|
+
else
|
88
|
+
# Calculate by elasped time
|
89
|
+
args = [@now.year, @now.month, @now.day, @now.hour, @now.min, @now.sec]
|
90
|
+
args = args[0..-(ALIGN[periods].size+1)]
|
91
|
+
args += ALIGN[periods]
|
92
|
+
to = Time.utc(*args)
|
93
|
+
to += (7-@now.wday)*60*60*24 if [:weeks,:week].include?(periods)
|
94
|
+
from = to - NUMHOURS[periods]*60*60*num+1
|
95
|
+
end
|
96
|
+
@from = from
|
97
|
+
@to = to
|
14
98
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
99
|
+
|
100
|
+
# Subtract num months from a given Time
|
101
|
+
def dec_months(num, time)
|
102
|
+
# Go back any 12 month intervals (aka years)
|
103
|
+
newy = time.year - num/12
|
104
|
+
num = num%12
|
105
|
+
# Go back the remainder months
|
106
|
+
newm = time.month - num
|
107
|
+
if newm < 1
|
108
|
+
newm = 12 - (-newm)
|
109
|
+
newy -= 1
|
27
110
|
end
|
111
|
+
from = Time.utc(newy, newm, time.day, time.hour, time.min, time.sec)
|
28
112
|
end
|
29
113
|
end
|
30
114
|
end
|
data/lib/charts.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module SAAL
|
2
|
+
class Charts
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(conffile=SAAL::CHARTSCONF, opts={})
|
6
|
+
@defs = YAML::load(File.new(conffile))
|
7
|
+
@sensors = opts[:sensors] || Sensors.new
|
8
|
+
@charts = {}
|
9
|
+
@defs.each do |name, defs|
|
10
|
+
@charts[name.to_sym] = Chart.new(name, defs, @sensors, opts)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Fetch a specific chart by name
|
15
|
+
def find(name)
|
16
|
+
@charts[name.to_sym]
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
@charts.each{|name, chart| yield chart}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/daemon.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module SAAL
|
2
2
|
class ForkedRunner
|
3
3
|
def self.run_as_fork(opts={})
|
4
|
-
|
5
|
-
|
6
|
-
$stderr.reopen "/dev/null", "a"
|
7
|
-
$stdin.reopen "/dev/null", "a"
|
8
|
-
$stdout.reopen "/dev/null", "a"
|
9
|
-
end
|
4
|
+
if opts[:foreground]
|
5
|
+
$stderr.puts "Running saal_daemon #{SAAL::VERSION} in foreground..."
|
10
6
|
yield ForkedRunner.new
|
7
|
+
else
|
8
|
+
fork do
|
9
|
+
if not opts[:keep_stdin]
|
10
|
+
$stderr.reopen "/dev/null", "a"
|
11
|
+
$stdin.reopen "/dev/null", "a"
|
12
|
+
$stdout.reopen "/dev/null", "a"
|
13
|
+
end
|
14
|
+
yield ForkedRunner.new
|
15
|
+
end
|
11
16
|
end
|
12
17
|
end
|
13
18
|
|
data/lib/dbstore.rb
CHANGED
@@ -67,7 +67,7 @@ module SAAL
|
|
67
67
|
Mysql.quote(text)
|
68
68
|
end
|
69
69
|
|
70
|
-
def db_query(query)
|
70
|
+
def db_query(query, opts={})
|
71
71
|
db = nil
|
72
72
|
begin
|
73
73
|
# connect to the MySQL server
|
@@ -77,7 +77,7 @@ module SAAL
|
|
77
77
|
res = db.query(query)
|
78
78
|
yield res if block_given?
|
79
79
|
rescue Mysql::Error => e
|
80
|
-
$stderr.puts "MySQL Error
|
80
|
+
$stderr.puts "MySQL Error #{e.errno}: #{e.error}" if !(e.errno == opts[:ignoreerr])
|
81
81
|
ensure
|
82
82
|
db.close if db
|
83
83
|
end
|
data/lib/dinrelay.rb
CHANGED
@@ -10,6 +10,10 @@ module SAAL
|
|
10
10
|
@og = outletgroup
|
11
11
|
end
|
12
12
|
|
13
|
+
def sensor_type
|
14
|
+
:onoff
|
15
|
+
end
|
16
|
+
|
13
17
|
def read(uncached = false)
|
14
18
|
{'ON' => 1.0, 'OFF' => 0.0}[@og.state(@num)]
|
15
19
|
end
|
@@ -33,25 +37,30 @@ module SAAL
|
|
33
37
|
|
34
38
|
def state(num)
|
35
39
|
response = do_get('/index.htm')
|
36
|
-
return parse_index_html(response.body)[num]
|
40
|
+
return response ? parse_index_html(response.body)[num] : nil
|
37
41
|
end
|
38
42
|
|
39
43
|
def set_state(num, state)
|
40
44
|
response = do_get("/outlet?#{num}=#{state}")
|
41
|
-
response
|
45
|
+
response != nil
|
42
46
|
end
|
43
47
|
|
44
48
|
private
|
45
49
|
def do_get(path)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
begin
|
51
|
+
Net::HTTP.start(@host,@port) do |http|
|
52
|
+
req = Net::HTTP::Get.new(path)
|
53
|
+
req.basic_auth @user, @pass
|
54
|
+
response = http.request(req)
|
55
|
+
if response.code != "200"
|
56
|
+
#$stderr.puts "ERROR: Code #{response.code}"
|
57
|
+
#$stderr.puts response.body
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
return response
|
53
61
|
end
|
54
|
-
|
62
|
+
rescue Exception
|
63
|
+
return nil
|
55
64
|
end
|
56
65
|
end
|
57
66
|
|
data/lib/outliercache.rb
CHANGED
@@ -13,7 +13,7 @@ module SAAL
|
|
13
13
|
# Sets how close the central values have to be for the cache to be "live"
|
14
14
|
MAX_CACHE_DEVIATION = 0.05
|
15
15
|
# Sets how off the read value can be from the cache median to be accepted
|
16
|
-
MAX_VALUE_DEVIATION = 0.
|
16
|
+
MAX_VALUE_DEVIATION = 0.15
|
17
17
|
|
18
18
|
def initialize
|
19
19
|
@compcache = []
|
data/lib/owsensor.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
module SAAL
|
2
2
|
class OWSensor < SensorUnderlying
|
3
|
-
attr_reader :serial
|
3
|
+
attr_reader :serial, :sensor_type
|
4
4
|
def initialize(defs, opts={})
|
5
5
|
@serial = defs['serial']
|
6
6
|
@connect_opts = {}
|
7
7
|
@connect_opts[:server] = defs['server'] if defs['server']
|
8
8
|
@connect_opts[:port] = defs['port'] if defs['port']
|
9
9
|
@owconn = opts[:owconn]
|
10
|
+
|
11
|
+
basename = File.basename(@serial)
|
12
|
+
@sensor_type = basename.to_sym if ['pressure','temperature','humidity'].include?(basename)
|
10
13
|
end
|
11
14
|
|
12
15
|
def read(uncached = false)
|
data/lib/saal.rb
CHANGED
@@ -8,8 +8,9 @@ module SAAL
|
|
8
8
|
CONFDIR = "/etc/saal/"
|
9
9
|
SENSORSCONF = CONFDIR+"sensors.yml"
|
10
10
|
DBCONF = CONFDIR+"database.yml"
|
11
|
+
CHARTSCONF = CONFDIR+"charts.yml"
|
11
12
|
|
12
|
-
VERSION = '0.2.
|
13
|
+
VERSION = '0.2.11'
|
13
14
|
end
|
14
15
|
|
15
16
|
require File.dirname(__FILE__)+'/dbstore.rb'
|
@@ -17,6 +18,8 @@ require File.dirname(__FILE__)+'/sensors.rb'
|
|
17
18
|
require File.dirname(__FILE__)+'/sensor.rb'
|
18
19
|
require File.dirname(__FILE__)+'/owsensor.rb'
|
19
20
|
require File.dirname(__FILE__)+'/daemon.rb'
|
21
|
+
require File.dirname(__FILE__)+'/charts.rb'
|
22
|
+
require File.dirname(__FILE__)+'/chart.rb'
|
20
23
|
require File.dirname(__FILE__)+'/chart_data.rb'
|
21
24
|
require File.dirname(__FILE__)+'/outliercache.rb'
|
22
25
|
require File.dirname(__FILE__)+'/dinrelay.rb'
|
data/lib/sensor.rb
CHANGED
@@ -3,6 +3,7 @@ module SAAL
|
|
3
3
|
end
|
4
4
|
|
5
5
|
class SensorUnderlying
|
6
|
+
def sensor_type; nil; end
|
6
7
|
def writeable?; false; end
|
7
8
|
def self.writeable!
|
8
9
|
define_method(:writeable?){true}
|
@@ -26,14 +27,24 @@ module SAAL
|
|
26
27
|
@min_value = defs['min_value']
|
27
28
|
@min_correctable = defs['min_correctable']
|
28
29
|
|
30
|
+
@read_offset = if defs['altitude'] && @underlying.sensor_type == :pressure
|
31
|
+
defs['altitude'].to_f/9.2
|
32
|
+
else
|
33
|
+
0.0
|
34
|
+
end
|
35
|
+
|
29
36
|
# Outliercache
|
30
37
|
@outliercache = opts[:no_outliercache] ? nil : OutlierCache.new
|
31
|
-
end
|
38
|
+
end
|
32
39
|
|
33
40
|
def writeable?
|
34
41
|
@underlying.writeable?
|
35
42
|
end
|
36
43
|
|
44
|
+
def sensor_type
|
45
|
+
@underlying.sensor_type
|
46
|
+
end
|
47
|
+
|
37
48
|
def read
|
38
49
|
outlier_proof_read(false)
|
39
50
|
end
|
@@ -52,22 +63,22 @@ module SAAL
|
|
52
63
|
|
53
64
|
def average(from, to)
|
54
65
|
return @mock_opts[:average] if @mock_opts[:average]
|
55
|
-
@dbstore.average(@name, from, to)
|
66
|
+
apply_offset @dbstore.average(@name, from, to)
|
56
67
|
end
|
57
68
|
|
58
69
|
def minimum(from, to)
|
59
70
|
return @mock_opts[:minimum] if @mock_opts[:minimum]
|
60
|
-
@dbstore.minimum(@name, from, to)
|
71
|
+
apply_offset @dbstore.minimum(@name, from, to)
|
61
72
|
end
|
62
73
|
|
63
74
|
def maximum(from, to)
|
64
75
|
return @mock_opts[:maximum] if @mock_opts[:maximum]
|
65
|
-
@dbstore.maximum(@name, from, to)
|
76
|
+
apply_offset @dbstore.maximum(@name, from, to)
|
66
77
|
end
|
67
78
|
|
68
79
|
def store_value
|
69
80
|
value = read_uncached
|
70
|
-
@dbstore.write(@name, Time.now.utc.to_i, value) if value
|
81
|
+
@dbstore.write(@name, Time.now.utc.to_i, value-@read_offset) if value
|
71
82
|
end
|
72
83
|
|
73
84
|
def mock_set(opts)
|
@@ -87,14 +98,18 @@ module SAAL
|
|
87
98
|
normalize(value)
|
88
99
|
end
|
89
100
|
|
101
|
+
def apply_offset(v)
|
102
|
+
v ? v+@read_offset : v
|
103
|
+
end
|
104
|
+
|
90
105
|
def normalize(value)
|
91
|
-
if @max_value and value > @max_value
|
106
|
+
apply_offset(if @max_value and value > @max_value
|
92
107
|
(@max_correctable and value <= @max_correctable) ? @max_value : nil
|
93
108
|
elsif @min_value and value < @min_value
|
94
109
|
(@min_correctable and value >= @min_correctable) ? @min_value : nil
|
95
110
|
else
|
96
111
|
value
|
97
|
-
end
|
112
|
+
end)
|
98
113
|
end
|
99
114
|
end
|
100
115
|
end
|
data/lib/sensors.rb
CHANGED
@@ -11,8 +11,7 @@ module SAAL
|
|
11
11
|
@sensors = {}
|
12
12
|
@defs.each do |name, defs|
|
13
13
|
self.class.sensors_from_defs(@dbstore, name, defs).each{|s| @sensors[s.name] = s}
|
14
|
-
end
|
15
|
-
|
14
|
+
end
|
16
15
|
end
|
17
16
|
|
18
17
|
# Implements the get methods to fetch a specific sensor
|
@@ -36,7 +35,9 @@ module SAAL
|
|
36
35
|
elsif defs['dinrelay']
|
37
36
|
og = DINRelay::OutletGroup.new(defs['dinrelay'])
|
38
37
|
outlet_names = defs['dinrelay']['outlets'] || []
|
38
|
+
outlet_descriptions = defs['dinrelay']['descriptions'] || []
|
39
39
|
return outlet_names.map do |num, oname|
|
40
|
+
defs.merge!('name' => outlet_descriptions[num])
|
40
41
|
Sensor.new(dbstore, oname, DINRelay::Outlet.new(num.to_i, og), defs, opts)
|
41
42
|
end
|
42
43
|
else
|
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.
|
10
|
-
s.date = '
|
9
|
+
s.version = '0.2.11'
|
10
|
+
s.date = '2011-05-26'
|
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
|
@@ -46,7 +46,9 @@ EOF
|
|
46
46
|
bin/saal_dump_database
|
47
47
|
bin/saal_import_mysql
|
48
48
|
bin/saal_readall
|
49
|
+
lib/chart.rb
|
49
50
|
lib/chart_data.rb
|
51
|
+
lib/charts.rb
|
50
52
|
lib/daemon.rb
|
51
53
|
lib/dbstore.rb
|
52
54
|
lib/dinrelay.rb
|
@@ -57,6 +59,8 @@ EOF
|
|
57
59
|
lib/sensors.rb
|
58
60
|
saal.gemspec
|
59
61
|
test/chart_data_test.rb
|
62
|
+
test/chart_test.rb
|
63
|
+
test/charts_test.rb
|
60
64
|
test/daemon_test.rb
|
61
65
|
test/dbstore_test.rb
|
62
66
|
test/dinrelay.html.erb
|
@@ -65,6 +69,7 @@ EOF
|
|
65
69
|
test/outliercache_test.rb
|
66
70
|
test/sensor_test.rb
|
67
71
|
test/sensors_test.rb
|
72
|
+
test/test_charts.yml
|
68
73
|
test/test_db.yml
|
69
74
|
test/test_dinrelay_sensors.yml
|
70
75
|
test/test_helper.rb
|
data/test/chart_data_test.rb
CHANGED
@@ -18,22 +18,115 @@ class MockSensor
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
class TestChartData < Test::Unit::TestCase
|
22
|
-
def
|
21
|
+
class TestChartData < Test::Unit::TestCase
|
22
|
+
def test_basic_range
|
23
23
|
sensor = MockSensor.new
|
24
|
-
|
25
|
-
assert_equal MOCK_AVERAGES,
|
26
|
-
assert_equal([[
|
24
|
+
range = SAAL::ChartDataRange.new(:from => 1, :to => 1000)
|
25
|
+
assert_equal MOCK_AVERAGES, range.get_data(:average, sensor, 5)
|
26
|
+
assert_equal([[1,200],[201,400],[401,600],[601,800],[801,1000]],
|
27
27
|
sensor.asked_averages)
|
28
28
|
end
|
29
|
-
|
30
|
-
def
|
29
|
+
|
30
|
+
def test_interval_range
|
31
|
+
sensor = MockSensor.new
|
32
|
+
now = Time.utc(2010, 12, 30, 15, 38, 19)
|
33
|
+
ranges = [[1293638400,1293655679],[1293655680,1293672959],
|
34
|
+
[1293672960,1293690239],[1293690240,1293707519],
|
35
|
+
[1293707520,1293724799]]
|
36
|
+
range = SAAL::ChartDataRange.new(:last => 24, :periods => :hours, :now => now)
|
37
|
+
assert_equal MOCK_AVERAGES, range.get_data(:average, sensor, 5)
|
38
|
+
assert_equal ranges, sensor.asked_averages
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_correct_time_use
|
31
42
|
sensor = MockSensor.new
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
assert_equal
|
37
|
-
|
43
|
+
range = SAAL::ChartDataRange.new(:last => 24, :periods => :hours)
|
44
|
+
now = Time.now.utc
|
45
|
+
to = Time.utc(now.year,now.month,now.day,now.hour,59,59).to_i
|
46
|
+
from = to - 24*60*60 + 1
|
47
|
+
assert_equal MOCK_AVERAGES[0..0], range.get_data(:average, sensor, 1)
|
48
|
+
assert_equal [[from,to]], sensor.asked_averages
|
49
|
+
end
|
50
|
+
|
51
|
+
# Test all the alignment functions underlying :last, :periods
|
52
|
+
def self.assert_alignment_interval(num,periods,from,to, periodnames=nil,
|
53
|
+
now = nil, extra=nil)
|
54
|
+
define_method("test_alignment_#{num}#{periods}#{extra.to_s}") do
|
55
|
+
now = now || Time.utc(2010, 12, 30, 15, 38, 19)
|
56
|
+
o = SAAL::ChartDataRange.new(:last => num, :periods => periods, :now => now)
|
57
|
+
assert_equal [from.to_i, to.to_i], [o.from, o.to],
|
58
|
+
"Expecting #{from.utc} - #{to.utc}\n"+
|
59
|
+
"Got #{Time.at(o.from).utc} - #{Time.at(o.to).utc}"
|
60
|
+
assert_equal periodnames, o.periodnames if periodnames
|
61
|
+
end
|
62
|
+
end
|
63
|
+
assert_alignment_interval(24, :hours, Time.utc(2010, 12, 29, 16, 0, 0),
|
64
|
+
Time.utc(2010, 12, 30, 15, 59, 59),
|
65
|
+
(16..23).map{|s| s.to_s}+(0..15).map{|s| s.to_s})
|
66
|
+
|
67
|
+
assert_alignment_interval(22, :hours, Time.utc(2010, 12, 29, 18, 0, 0),
|
68
|
+
Time.utc(2010, 12, 30, 15, 59, 59),
|
69
|
+
(18..23).map{|s| s.to_s}+(0..15).map{|s| s.to_s})
|
70
|
+
|
71
|
+
assert_alignment_interval(1, :days, Time.utc(2010, 12, 30, 0, 0, 0),
|
72
|
+
Time.utc(2010, 12, 30, 23, 59, 59),
|
73
|
+
["Thu"])
|
74
|
+
|
75
|
+
assert_alignment_interval(12, :hours, Time.utc(2010, 12, 30, 4, 0, 0),
|
76
|
+
Time.utc(2010, 12, 30, 15, 59, 59),
|
77
|
+
(4..15).map{|s| s.to_s})
|
78
|
+
|
79
|
+
assert_alignment_interval(1, :weeks,Time.utc(2010, 12, 27, 0, 0, 0),
|
80
|
+
Time.utc(2011, 1, 2, 23, 59, 59),
|
81
|
+
["27 Dec"])
|
82
|
+
|
83
|
+
assert_alignment_interval(2, :weeks,Time.utc(2010, 12, 20, 0, 0, 0),
|
84
|
+
Time.utc(2011, 1, 2, 23, 59, 59),
|
85
|
+
["20 Dec","27 Dec"])
|
86
|
+
|
87
|
+
assert_alignment_interval(1, :years, Time.utc(2010, 1, 1, 0, 0, 0),
|
88
|
+
Time.utc(2010, 12, 31, 23, 59, 59),
|
89
|
+
["2010"])
|
90
|
+
|
91
|
+
assert_alignment_interval(2, :years, Time.utc(2009, 1, 1, 0, 0, 0),
|
92
|
+
Time.utc(2010, 12, 31, 23, 59, 59),
|
93
|
+
["2009","2010"])
|
94
|
+
|
95
|
+
assert_alignment_interval(1, :months, Time.utc(2010, 12, 1, 0, 0, 0),
|
96
|
+
Time.utc(2010, 12, 31, 23, 59, 59),
|
97
|
+
["Dec"])
|
98
|
+
assert_alignment_interval(1, :months, Time.utc(2010, 4, 1, 0, 0, 0),
|
99
|
+
Time.utc(2010, 4, 30, 23, 59, 59),
|
100
|
+
["Apr"], Time.utc(2010, 4, 30, 12, 50, 30),
|
101
|
+
"_30day_month")
|
102
|
+
|
103
|
+
assert_alignment_interval(12, :months, Time.utc(2010, 1, 1, 0, 0, 0),
|
104
|
+
Time.utc(2010, 12, 31, 23, 59, 59),
|
105
|
+
["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug",
|
106
|
+
"Sep","Oct","Nov","Dec"])
|
107
|
+
|
108
|
+
assert_alignment_interval(13, :months, Time.utc(2009, 12, 1, 0, 0, 0),
|
109
|
+
Time.utc(2010, 12, 31, 23, 59, 59))
|
110
|
+
assert_alignment_interval(24, :months, Time.utc(2009, 1, 1, 0, 0, 0),
|
111
|
+
Time.utc(2010, 12, 31, 23, 59, 59))
|
112
|
+
assert_alignment_interval(12, :months, Time.utc(2010, 2, 1, 0, 0, 0),
|
113
|
+
Time.utc(2011, 1, 31, 23, 59, 59),
|
114
|
+
["Feb","Mar","Apr","May","Jun","Jul","Aug",
|
115
|
+
"Sep","Oct","Nov","Dec","Jan"],
|
116
|
+
Time.utc(2011, 1, 1, 14, 15, 10),
|
117
|
+
"_midyear")
|
118
|
+
|
119
|
+
def self.assert_dec_months(num, from, to)
|
120
|
+
define_method("test_dec_#{num}months_from#{from.join('-')}_to#{to.join('-')}") do
|
121
|
+
o = SAAL::ChartDataRange.new
|
122
|
+
assert_equal Time.utc(to[0], to[1], 1, 0, 0, 0),
|
123
|
+
o.send(:dec_months, num, Time.utc(from[0],from[1],1,0,0,0))
|
124
|
+
end
|
38
125
|
end
|
126
|
+
|
127
|
+
assert_dec_months 2, [2010,12],[2010,10]
|
128
|
+
assert_dec_months 12, [2011,1],[2010,1]
|
129
|
+
assert_dec_months 24, [2011,1],[2009,1]
|
130
|
+
assert_dec_months 2, [2011,1],[2010,11]
|
131
|
+
assert_dec_months 14, [2011,1],[2009,11]
|
39
132
|
end
|
data/test/chart_test.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper.rb'
|
2
|
+
|
3
|
+
class TestChart < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@defs = YAML::load File.new(TEST_CHARTS_FILE)
|
6
|
+
sensors = SAAL::Sensors.new(TEST_SENSORS_FILE, TEST_DBCONF)
|
7
|
+
@charts = SAAL::Charts.new(TEST_CHARTS_FILE,
|
8
|
+
:sensors => sensors,
|
9
|
+
:now => Time.utc(2010, 12, 30, 15, 38, 19))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_alignlabels
|
13
|
+
assert_equal :center, @charts.find('week').alignlabels
|
14
|
+
assert_equal :left, @charts.find('4week').alignlabels
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_average
|
18
|
+
name = 'week'
|
19
|
+
defs = @defs[name]
|
20
|
+
chart = @charts.find(name)
|
21
|
+
assert_equal ['Fri','Sat','Sun','Mon','Tue','Wed','Thu'], chart.periodnames
|
22
|
+
chart.sensors.each {|s| s.mock_set(:average => 1)}
|
23
|
+
assert_equal({:fake_temp => [1], :non_existant => [1]}, chart.average(1))
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_from_to
|
27
|
+
chart = @charts.find('day')
|
28
|
+
assert_equal Time.utc(2010, 12, 29, 16, 0, 0).to_i, chart.from
|
29
|
+
assert_equal Time.utc(2010, 12, 30, 15, 59, 59).to_i, chart.to
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_min_max_avg_1arity
|
33
|
+
name = 'week'
|
34
|
+
defs = @defs[name]
|
35
|
+
chart = @charts.find(name)
|
36
|
+
assert_equal ['Fri','Sat','Sun','Mon','Tue','Wed','Thu'], chart.periodnames
|
37
|
+
v = {:minimum => 1.0, :maximum => 2.0, :average => 1.5}
|
38
|
+
[:minimum,:maximum,:average].each do |method|
|
39
|
+
chart.sensors.each {|s| s.mock_set(method => v[method])}
|
40
|
+
assert_equal({:fake_temp => [v[method]], :non_existant => [v[method]]}, chart.send(method,1))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_min_max_0arity
|
45
|
+
name = 'week'
|
46
|
+
defs = @defs[name]
|
47
|
+
chart = @charts.find(name)
|
48
|
+
assert_equal ['Fri','Sat','Sun','Mon','Tue','Wed','Thu'], chart.periodnames
|
49
|
+
v = {:minimum => 1.0, :maximum => 2.0, :average => 1.5}
|
50
|
+
[:minimum,:maximum,:average].each do |method|
|
51
|
+
chart.sensors.each {|s| s.mock_set(method => v[method])}
|
52
|
+
assert_equal({:fake_temp => v[method], :non_existant => v[method]}, chart.send(method))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/test/charts_test.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper.rb'
|
2
|
+
|
3
|
+
class TestCharts < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@defs = YAML::load File.new(TEST_CHARTS_FILE)
|
6
|
+
sensors = SAAL::Sensors.new(TEST_SENSORS_FILE, TEST_DBCONF)
|
7
|
+
@charts = SAAL::Charts.new(TEST_CHARTS_FILE, :sensors => sensors)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_load
|
11
|
+
@defs.each do |name, defs|
|
12
|
+
chart = @charts.find(name)
|
13
|
+
assert_instance_of SAAL::Chart, chart
|
14
|
+
assert_equal name, chart.name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_each
|
19
|
+
i = 0
|
20
|
+
@charts.each do |chart|
|
21
|
+
defs = @defs[chart.name]
|
22
|
+
assert_equal defs['last'], chart.num
|
23
|
+
assert_equal defs['periods'], chart.periods
|
24
|
+
assert_equal defs['description'], chart.description
|
25
|
+
assert_equal defs['alt'], chart.alt
|
26
|
+
assert_equal defs['sensors'], chart.sensors.map{|s| s.name.to_s}
|
27
|
+
i += 1
|
28
|
+
end
|
29
|
+
assert_equal @defs.size, i, "Charts.each did not iterate correctly"
|
30
|
+
end
|
31
|
+
end
|
data/test/dinrelay_test.rb
CHANGED
@@ -14,6 +14,7 @@ class TestDINRelay < Test::Unit::TestCase
|
|
14
14
|
@html = opts[:html]
|
15
15
|
@user = opts[:user]
|
16
16
|
@pass = opts[:pass]
|
17
|
+
@status = opts[:status] || 200
|
17
18
|
@feedback = opts[:feedback] || {}
|
18
19
|
end
|
19
20
|
def do_GET(req, res)
|
@@ -22,7 +23,8 @@ class TestDINRelay < Test::Unit::TestCase
|
|
22
23
|
user == @user && pass == @pass
|
23
24
|
}
|
24
25
|
res.body = @html
|
25
|
-
res
|
26
|
+
res.status = @status
|
27
|
+
res['Content-Type'] = "text/html"
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
@@ -84,6 +86,13 @@ class TestDINRelay < Test::Unit::TestCase
|
|
84
86
|
def test_enumerate_sensors
|
85
87
|
sensors = SAAL::Sensors.new(TEST_SENSORS_DINRELAY_FILE, TEST_DBCONF)
|
86
88
|
assert_equal((1..8).map{|i| "name#{i}"}, sensors.map{|s| s.name}.sort)
|
89
|
+
assert_equal((1..8).map{|i| "description#{i}"}, sensors.map{|s| s.description}.sort)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_sensor_type
|
93
|
+
SAAL::Sensors.new(TEST_SENSORS_DINRELAY_FILE, TEST_DBCONF).each do |s|
|
94
|
+
assert_equal :onoff, s.sensor_type
|
95
|
+
end
|
87
96
|
end
|
88
97
|
|
89
98
|
def test_read_sensors
|
@@ -109,4 +118,20 @@ class TestDINRelay < Test::Unit::TestCase
|
|
109
118
|
end
|
110
119
|
end
|
111
120
|
end
|
121
|
+
|
122
|
+
def test_failed_connection
|
123
|
+
@vals.each do |num, state|
|
124
|
+
assert_equal nil, @og.state(num)
|
125
|
+
assert !@og.set_state(num,"ON"), "State change working without a server?!"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_failed_request
|
130
|
+
with_webrick(:html=>create_index_html(@vals),:status=>404) do |feedback|
|
131
|
+
@vals.each do |num, state|
|
132
|
+
assert_equal nil, @og.state(num)
|
133
|
+
assert !@og.set_state(num,"ON"), "State change working without a server?!"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
112
137
|
end
|
data/test/sensor_test.rb
CHANGED
@@ -11,10 +11,19 @@ class MockConnection
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class MockDBStore
|
14
|
-
attr_accessor :value
|
14
|
+
attr_accessor :value, :stored_value
|
15
15
|
def average(sensor, from, to)
|
16
16
|
@value
|
17
17
|
end
|
18
|
+
def minimum(sensor, from, to)
|
19
|
+
@value
|
20
|
+
end
|
21
|
+
def maximum(sensor, from, to)
|
22
|
+
@value
|
23
|
+
end
|
24
|
+
def write(sensor,date,value)
|
25
|
+
@stored_value = value
|
26
|
+
end
|
18
27
|
end
|
19
28
|
|
20
29
|
class TestSensor < Test::Unit::TestCase
|
@@ -92,6 +101,33 @@ class TestSensor < Test::Unit::TestCase
|
|
92
101
|
assert_equal [0]*20+[1000], (1..21).map{@fake3.read_uncached}
|
93
102
|
end
|
94
103
|
|
104
|
+
def test_eliminate_outliers
|
105
|
+
correctread = 994.422
|
106
|
+
fakeread = 817.309
|
107
|
+
@conn.values = [correctread]*20 + [fakeread,correctread]
|
108
|
+
assert_equal [correctread]*21, (1..21).map{@fake3.read}
|
109
|
+
@conn.values = [correctread]*20 + [fakeread,correctread]
|
110
|
+
assert_equal [correctread]*21, (1..21).map{@fake3.read_uncached}
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_sealevel_correction
|
114
|
+
sensor = fake_sensor('pressure')
|
115
|
+
@conn.value = @dbstore.value = 1000
|
116
|
+
corrected = 1000+@defs['pressure']['altitude'].to_f/9.2
|
117
|
+
assert_equal corrected, sensor.read
|
118
|
+
sensor.store_value
|
119
|
+
assert_equal 1000, @dbstore.stored_value
|
120
|
+
assert_equal corrected, sensor.minimum(0,100)
|
121
|
+
assert_equal corrected, sensor.maximum(0,100)
|
122
|
+
assert_equal corrected, sensor.average(0,100)
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_sensor_type
|
126
|
+
[:pressure, :humidity, :temperature].each do |type|
|
127
|
+
assert_equal type, fake_sensor(type.to_s).sensor_type
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
95
131
|
def test_mocked
|
96
132
|
@mockable = fake_sensor('fake3')
|
97
133
|
@conn.value = 1.0
|
@@ -0,0 +1,27 @@
|
|
1
|
+
day:
|
2
|
+
sensors: [fake_temp, non_existant]
|
3
|
+
last: 24
|
4
|
+
periods: hours
|
5
|
+
alt: Some ALT text
|
6
|
+
description: some description of the chart
|
7
|
+
|
8
|
+
week:
|
9
|
+
sensors: [fake_temp, non_existant]
|
10
|
+
last: 7
|
11
|
+
periods: days
|
12
|
+
|
13
|
+
4week:
|
14
|
+
sensors: [fake_temp, non_existant]
|
15
|
+
last: 4
|
16
|
+
periods: weeks
|
17
|
+
alignlabels: left
|
18
|
+
|
19
|
+
year:
|
20
|
+
sensors: [fake_temp, non_existant]
|
21
|
+
last: 12
|
22
|
+
periods: months
|
23
|
+
|
24
|
+
4year:
|
25
|
+
sensors: [fake_temp, non_existant]
|
26
|
+
last: 4
|
27
|
+
periods: years
|
data/test/test_helper.rb
CHANGED
@@ -8,6 +8,7 @@ class Test::Unit::TestCase
|
|
8
8
|
TEST_SENSORS_DINRELAY_FILE = File.dirname(__FILE__)+'/test_dinrelay_sensors.yml'
|
9
9
|
TEST_SENSOR_CLEANUPS_FILE = File.dirname(__FILE__)+'/test_sensor_cleanups.yml'
|
10
10
|
TEST_NONEXIST_SENSOR_FILE = File.dirname(__FILE__)+'/nonexistant_sensor.yml'
|
11
|
+
TEST_CHARTS_FILE = File.dirname(__FILE__)+'/test_charts.yml'
|
11
12
|
TEST_DBCONF = File.dirname(__FILE__)+'/test_db.yml'
|
12
13
|
TEST_DBOPTS = YAML::load(File.new(TEST_DBCONF))
|
13
14
|
|
@@ -19,3 +19,19 @@ fake3:
|
|
19
19
|
onewire:
|
20
20
|
serial: /10.4AEC29CDBAAB/temperature
|
21
21
|
|
22
|
+
pressure:
|
23
|
+
name: "A fake pressure sensor"
|
24
|
+
altitude: 200
|
25
|
+
onewire:
|
26
|
+
serial: /10.4AEC29CDBAAB/pressure
|
27
|
+
temperature:
|
28
|
+
name: "A fake pressure sensor"
|
29
|
+
onewire:
|
30
|
+
serial: /10.4AEC29CDBAAB/temperature
|
31
|
+
humidity:
|
32
|
+
name: "A fake pressure sensor"
|
33
|
+
onewire:
|
34
|
+
serial: /10.4AEC29CDBAAB/humidity
|
35
|
+
|
36
|
+
|
37
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 1
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 11
|
10
|
+
version: 0.2.11
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- "Pedro C\xC3\xB4rte-Real"
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-05-26 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -94,7 +94,9 @@ files:
|
|
94
94
|
- bin/saal_dump_database
|
95
95
|
- bin/saal_import_mysql
|
96
96
|
- bin/saal_readall
|
97
|
+
- lib/chart.rb
|
97
98
|
- lib/chart_data.rb
|
99
|
+
- lib/charts.rb
|
98
100
|
- lib/daemon.rb
|
99
101
|
- lib/dbstore.rb
|
100
102
|
- lib/dinrelay.rb
|
@@ -105,6 +107,8 @@ files:
|
|
105
107
|
- lib/sensors.rb
|
106
108
|
- saal.gemspec
|
107
109
|
- test/chart_data_test.rb
|
110
|
+
- test/chart_test.rb
|
111
|
+
- test/charts_test.rb
|
108
112
|
- test/daemon_test.rb
|
109
113
|
- test/dbstore_test.rb
|
110
114
|
- test/dinrelay.html.erb
|
@@ -113,6 +117,7 @@ files:
|
|
113
117
|
- test/outliercache_test.rb
|
114
118
|
- test/sensor_test.rb
|
115
119
|
- test/sensors_test.rb
|
120
|
+
- test/test_charts.yml
|
116
121
|
- test/test_db.yml
|
117
122
|
- test/test_dinrelay_sensors.yml
|
118
123
|
- test/test_helper.rb
|
@@ -157,6 +162,8 @@ specification_version: 2
|
|
157
162
|
summary: Thin abstraction layer for interfacing and recording sensors (currently onewire) and actuators (currently dinrelay)
|
158
163
|
test_files:
|
159
164
|
- test/chart_data_test.rb
|
165
|
+
- test/chart_test.rb
|
166
|
+
- test/charts_test.rb
|
160
167
|
- test/daemon_test.rb
|
161
168
|
- test/dbstore_test.rb
|
162
169
|
- test/dinrelay_test.rb
|