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 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 outlier detection and removal
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 <day|week|4week|year|4year> <chart_file.png>")
7
+ $stderr.puts("Usage: saal_chart <chart dir>")
7
8
  end
8
9
 
9
- if ARGV.size != 2
10
+ if ARGV.size != 1
10
11
  usage
11
12
  exit (2)
12
13
  end
13
14
 
14
- @now = Time.now.utc
15
-
16
- NUM_VALUES = 500
17
- case ARGV[0]
18
- when 'day' then
19
- PERIODNAMES = (0..23).map{|i| ((@now.hour - i)%24).to_s}.reverse
20
- ALIGNMENT = :hour
21
- NUMDAYS = 1
22
- ALIGNNAMES = :center
23
- when 'week' then
24
- daynames = ["Seg","Ter","Qua","Qui","Sex","Sab","Dom"]
25
- PERIODNAMES = (1..7).map{|i| (@now.wday - i)%7}.map{|w| daynames[w]}.reverse
26
- ALIGNMENT = :day
27
- NUMDAYS = 7
28
- ALIGNNAMES = :center
29
- when '4week' then
30
- monthnames = ["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Sep","Out","Nov","Dez"]
31
- initial = @now.to_i - (@now.wday-1)*24*60*60
32
- PERIODNAMES = (0...4).map do |i|
33
- time = Time.at(initial - i*24*60*60*7)
34
- time.day.to_s+" "+ monthnames[time.month-1]
35
- end.reverse
36
- ALIGNMENT = :week
37
- NUMDAYS = 28
38
- ALIGNNAMES = :left
39
- when 'year' then
40
- monthnames = ["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Sep","Out","Nov","Dez"]
41
- initial = @now.to_i - (@now.wday-1)*24*60*60
42
- PERIODNAMES = (1..12).map{|i| (@now.month - i)%12}.map{|m| monthnames[m]}.reverse
43
- ALIGNMENT = :month
44
- NUMDAYS = 356
45
- ALIGNNAMES = :center
46
- when '4year' then
47
- PERIODNAMES = (0..3).map{|i| (@now.year - i).to_s}.reverse
48
- ALIGNMENT = :year
49
- NUMDAYS = 356*3
50
- ALIGNNAMES = :center
51
- else
52
- usage
53
- exit(3)
54
- end
55
- NUMPERIODS = PERIODNAMES.size
56
-
57
- align = {:year => [12,31,23,59,59],
58
- :month => [31,23,59,59],
59
- :day => [23,59,59],
60
- :week => [23,59,59],
61
- :hour => [59,59]}
62
-
63
- args = [@now.year, @now.month, @now.day, @now.hour, @now.min, @now.sec]
64
- args = args[0..-(align[ALIGNMENT].size+1)]
65
- args += align[ALIGNMENT]
66
- @to = Time.utc(*args).to_i
67
- @to += (6-@now.wday)*60*60*24 if ALIGNMENT == :week
68
- @from = @to - 60*60*24*NUMDAYS
69
-
70
- @sensors = SAAL::Sensors.new
71
- def create_data(sensor, min, max, constant=0)
72
- @c = SAAL::ChartData.new(@sensors.send(sensor))
73
- d = @c.get_data(@from, @to, NUM_VALUES)
74
- d = d.map{|num| num ? num+constant : num}
75
- @c.normalize_data(d,min,max)
76
- end
77
-
78
- @data = [create_data('temp_exterior', -15, 45),
79
- create_data('temp_estufa', -15, 45),
80
- create_data('hum_exterior', 0, 100),
81
- create_data('pressao', 950, 1050, 0.54*33.86)] #Convert to pressure at sea level
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
- $stderr.puts "Usage: saal_daemon [pidfile]"
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
- File.open(pidfile, 'w') do |f|
19
- f.write(pid)
20
- f.close
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 ChartData
3
- def initialize(sensor)
4
- @sensor = sensor
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 get_data(from, to, num)
8
- step = (to - from).to_f/num.to_f
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 = (from+i*step).to_i
11
- t = (from+(i+1)*step-0.5).to_i
12
- @sensor.average(f, t)
13
- end << @sensor.average((from+(num-1)*step).to_i, to)
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
- def normalize_data(data, min, max)
17
- data.map do |i|
18
- if i.nil?
19
- -1.0
20
- elsif i < min
21
- 0.0
22
- elsif i > max
23
- 100.0
24
- else
25
- v = (((i-min)/(max-min).to_f)*1000).round/10.0
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
- fork do
5
- if not opts[:keep_stdin]
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 \#{e.errno}: \#{e.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.code == "200"
45
+ response != nil
42
46
  end
43
47
 
44
48
  private
45
49
  def do_get(path)
46
- Net::HTTP.start(@host,@port) do |http|
47
- req = Net::HTTP::Get.new(path)
48
- req.basic_auth @user, @pass
49
- response = http.request(req)
50
- if response.code != "200"
51
- $stderr.puts "ERROR: Code #{response.code}"
52
- $stderr.puts response.body
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
- return response
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.25
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.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.2'
10
- s.date = '2010-12-29'
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
@@ -18,22 +18,115 @@ class MockSensor
18
18
  end
19
19
  end
20
20
 
21
- class TestChartData < Test::Unit::TestCase
22
- def test_get_data
21
+ class TestChartData < Test::Unit::TestCase
22
+ def test_basic_range
23
23
  sensor = MockSensor.new
24
- c = SAAL::ChartData.new(sensor)
25
- assert_equal MOCK_AVERAGES, c.get_data(0, 1000, 5)
26
- assert_equal([[0,199],[200,399],[400,599],[600,799],[800,1000]],
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 test_normalize_data
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
- c = SAAL::ChartData.new(sensor)
33
- d = c.get_data(0, 1000, 5)
34
- assert_equal NORMALIZED_MOCK_AVERAGES,
35
- c.normalize_data(d, MOCK_MIN, MOCK_MAX)
36
- assert_equal([[0,199],[200,399],[400,599],[600,799],[800,1000]],
37
- sensor.asked_averages)
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
@@ -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
@@ -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
@@ -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['Content-Type'] = "text/xml"
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
@@ -13,4 +13,12 @@ group1:
13
13
  6: name6
14
14
  7: name7
15
15
  8: name8
16
-
16
+ descriptions:
17
+ 1: description1
18
+ 2: description2
19
+ 3: description3
20
+ 4: description4
21
+ 5: description5
22
+ 6: description6
23
+ 7: description7
24
+ 8: description8
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: 19
4
+ hash: 1
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 2
10
- version: 0.2.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: 2010-12-29 00:00:00 +00:00
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