saal 0.2.2 → 0.2.11
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/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
|