saal 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +460 -0
- data/README.rdoc +76 -0
- data/Rakefile +160 -0
- data/TODO +24 -0
- data/bin/.gitignore +2 -0
- data/bin/dinrelayset +35 -0
- data/bin/dinrelaystatus +32 -0
- data/bin/saal_chart +110 -0
- data/bin/saal_daemon +22 -0
- data/bin/saal_dump_database +11 -0
- data/bin/saal_import_mysql +18 -0
- data/bin/saal_readall +10 -0
- data/lib/chart_data.rb +30 -0
- data/lib/daemon.rb +63 -0
- data/lib/dbstore.rb +86 -0
- data/lib/dinrelay.rb +67 -0
- data/lib/outliercache.rb +53 -0
- data/lib/owsensor.rb +21 -0
- data/lib/saal.rb +23 -0
- data/lib/sensor.rb +100 -0
- data/lib/sensors.rb +48 -0
- data/saal.gemspec +77 -0
- data/test/chart_data_test.rb +39 -0
- data/test/daemon_test.rb +44 -0
- data/test/dbstore_test.rb +70 -0
- data/test/dinrelay.html.erb +143 -0
- data/test/dinrelay_test.rb +112 -0
- data/test/nonexistant_sensor.yml +5 -0
- data/test/outliercache_test.rb +42 -0
- data/test/sensor_test.rb +109 -0
- data/test/sensors_test.rb +77 -0
- data/test/test_db.yml +4 -0
- data/test/test_dinrelay_sensors.yml +16 -0
- data/test/test_helper.rb +37 -0
- data/test/test_sensor_cleanups.yml +21 -0
- data/test/test_sensors.yml +10 -0
- metadata +166 -0
data/lib/outliercache.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module SAAL
|
2
|
+
class OutlierCache
|
3
|
+
# By feeding values into this cache the outliers are identified. The cache
|
4
|
+
# is conservative and only flags down values that it is sure are outliers.
|
5
|
+
# The cache considers itself "live" when the values in the cache are all
|
6
|
+
# within a few percent of each other and will then flag down outliers. When
|
7
|
+
# the cache is not live all values will be considered good.
|
8
|
+
|
9
|
+
COMP_CACHE_SIZE = 11 # Should be even so the median is well calculated
|
10
|
+
|
11
|
+
# These are conservative settings that can be made stricted if the cache is
|
12
|
+
# not rejecting enough values or is often not "live"
|
13
|
+
# Sets how close the central values have to be for the cache to be "live"
|
14
|
+
MAX_CACHE_DEVIATION = 0.05
|
15
|
+
# Sets how off the read value can be from the cache median to be accepted
|
16
|
+
MAX_VALUE_DEVIATION = 0.25
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@compcache = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def live
|
23
|
+
@compcache.size == COMP_CACHE_SIZE && valid_cache
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate(value)
|
27
|
+
ret = compare_with_cache(value)
|
28
|
+
@compcache.shift if @compcache.size == COMP_CACHE_SIZE
|
29
|
+
@compcache.push value
|
30
|
+
ret
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def compare_with_cache(value)
|
35
|
+
return true if !live
|
36
|
+
@compcache.sort!
|
37
|
+
median = @compcache[COMP_CACHE_SIZE/2]
|
38
|
+
(value.to_f/median.to_f - 1.0).abs < MAX_VALUE_DEVIATION
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid_cache
|
42
|
+
@compcache.sort!
|
43
|
+
central = @compcache[1..(@compcache.size-2)]
|
44
|
+
sum = central.inject(0.0){|sum,el| sum+el}
|
45
|
+
return false if sum == 0.0
|
46
|
+
average = sum/central.size
|
47
|
+
central.each do |el|
|
48
|
+
return false if (el.to_f/average.to_f - 1.0).abs > MAX_CACHE_DEVIATION
|
49
|
+
end
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/owsensor.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module SAAL
|
2
|
+
class OWSensor < SensorUnderlying
|
3
|
+
attr_reader :serial
|
4
|
+
def initialize(defs, opts={})
|
5
|
+
@serial = defs['serial']
|
6
|
+
@connect_opts = {}
|
7
|
+
@connect_opts[:server] = defs['server'] if defs['server']
|
8
|
+
@connect_opts[:port] = defs['port'] if defs['port']
|
9
|
+
@owconn = opts[:owconn]
|
10
|
+
end
|
11
|
+
|
12
|
+
def read(uncached = false)
|
13
|
+
@owconn ||= OWNet::Connection.new(@connect_opts)
|
14
|
+
begin
|
15
|
+
@owconn.read((uncached ? '/uncached' : '')+@serial)
|
16
|
+
rescue Exception
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/saal.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require "mysql"
|
3
|
+
require 'ownet'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'erb'
|
6
|
+
|
7
|
+
module SAAL
|
8
|
+
CONFDIR = "/etc/saal/"
|
9
|
+
SENSORSCONF = CONFDIR+"sensors.yml"
|
10
|
+
DBCONF = CONFDIR+"database.yml"
|
11
|
+
|
12
|
+
VERSION = '0.2.2'
|
13
|
+
end
|
14
|
+
|
15
|
+
require File.dirname(__FILE__)+'/dbstore.rb'
|
16
|
+
require File.dirname(__FILE__)+'/sensors.rb'
|
17
|
+
require File.dirname(__FILE__)+'/sensor.rb'
|
18
|
+
require File.dirname(__FILE__)+'/owsensor.rb'
|
19
|
+
require File.dirname(__FILE__)+'/daemon.rb'
|
20
|
+
require File.dirname(__FILE__)+'/chart_data.rb'
|
21
|
+
require File.dirname(__FILE__)+'/outliercache.rb'
|
22
|
+
require File.dirname(__FILE__)+'/dinrelay.rb'
|
23
|
+
|
data/lib/sensor.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module SAAL
|
2
|
+
class UnimplementedMethod < RuntimeError
|
3
|
+
end
|
4
|
+
|
5
|
+
class SensorUnderlying
|
6
|
+
def writeable?; false; end
|
7
|
+
def self.writeable!
|
8
|
+
define_method(:writeable?){true}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Sensor
|
13
|
+
MAX_READ_TRIES = 5
|
14
|
+
|
15
|
+
attr_reader :name, :description
|
16
|
+
def initialize(dbstore, name, underlying, defs, opts={})
|
17
|
+
@dbstore = dbstore
|
18
|
+
@name = name
|
19
|
+
@underlying = underlying
|
20
|
+
@description = defs['name']
|
21
|
+
@mock_opts = {}
|
22
|
+
|
23
|
+
# Reading correction settings
|
24
|
+
@max_value = defs['max_value']
|
25
|
+
@max_correctable = defs['max_correctable']
|
26
|
+
@min_value = defs['min_value']
|
27
|
+
@min_correctable = defs['min_correctable']
|
28
|
+
|
29
|
+
# Outliercache
|
30
|
+
@outliercache = opts[:no_outliercache] ? nil : OutlierCache.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def writeable?
|
34
|
+
@underlying.writeable?
|
35
|
+
end
|
36
|
+
|
37
|
+
def read
|
38
|
+
outlier_proof_read(false)
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_uncached
|
42
|
+
outlier_proof_read(true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def write(value)
|
46
|
+
if @mock_opts[:value]
|
47
|
+
@mock_opts[:value] = value
|
48
|
+
else
|
49
|
+
@underlying.write(value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def average(from, to)
|
54
|
+
return @mock_opts[:average] if @mock_opts[:average]
|
55
|
+
@dbstore.average(@name, from, to)
|
56
|
+
end
|
57
|
+
|
58
|
+
def minimum(from, to)
|
59
|
+
return @mock_opts[:minimum] if @mock_opts[:minimum]
|
60
|
+
@dbstore.minimum(@name, from, to)
|
61
|
+
end
|
62
|
+
|
63
|
+
def maximum(from, to)
|
64
|
+
return @mock_opts[:maximum] if @mock_opts[:maximum]
|
65
|
+
@dbstore.maximum(@name, from, to)
|
66
|
+
end
|
67
|
+
|
68
|
+
def store_value
|
69
|
+
value = read_uncached
|
70
|
+
@dbstore.write(@name, Time.now.utc.to_i, value) if value
|
71
|
+
end
|
72
|
+
|
73
|
+
def mock_set(opts)
|
74
|
+
@mock_opts.merge!(opts)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def outlier_proof_read(uncached)
|
79
|
+
return @mock_opts[:value] if @mock_opts[:value]
|
80
|
+
tries = 0
|
81
|
+
value = nil
|
82
|
+
begin
|
83
|
+
tries += 1
|
84
|
+
value = @underlying.read(uncached)
|
85
|
+
break if value && @outliercache && @outliercache.validate(value)
|
86
|
+
end while tries < MAX_READ_TRIES
|
87
|
+
normalize(value)
|
88
|
+
end
|
89
|
+
|
90
|
+
def normalize(value)
|
91
|
+
if @max_value and value > @max_value
|
92
|
+
(@max_correctable and value <= @max_correctable) ? @max_value : nil
|
93
|
+
elsif @min_value and value < @min_value
|
94
|
+
(@min_correctable and value >= @min_correctable) ? @min_value : nil
|
95
|
+
else
|
96
|
+
value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/sensors.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module SAAL
|
2
|
+
class UnknownSensorType < RuntimeError
|
3
|
+
end
|
4
|
+
|
5
|
+
class Sensors
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(conffile=SAAL::SENSORSCONF, dbconffile=SAAL::DBCONF)
|
9
|
+
@defs = YAML::load(File.new(conffile))
|
10
|
+
@dbstore = DBStore.new(dbconffile)
|
11
|
+
@sensors = {}
|
12
|
+
@defs.each do |name, defs|
|
13
|
+
self.class.sensors_from_defs(@dbstore, name, defs).each{|s| @sensors[s.name] = s}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
# Implements the get methods to fetch a specific sensor
|
19
|
+
def method_missing(name, *args)
|
20
|
+
name = name.to_s
|
21
|
+
if args.size == 0 && @sensors.include?(name)
|
22
|
+
@sensors[name]
|
23
|
+
else
|
24
|
+
raise NoMethodError, "undefined method \"#{name}\" for #{self}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def each
|
29
|
+
@sensors.each{|name, sensor| yield sensor}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.sensors_from_defs(dbstore, name, defs, opts={})
|
33
|
+
if defs['onewire']
|
34
|
+
return [Sensor.new(dbstore, name, OWSensor.new(defs['onewire'], opts),
|
35
|
+
defs, opts)]
|
36
|
+
elsif defs['dinrelay']
|
37
|
+
og = DINRelay::OutletGroup.new(defs['dinrelay'])
|
38
|
+
outlet_names = defs['dinrelay']['outlets'] || []
|
39
|
+
return outlet_names.map do |num, oname|
|
40
|
+
Sensor.new(dbstore, oname, DINRelay::Outlet.new(num.to_i, og), defs, opts)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise UnknownSensorType, "Couldn't figure out a valid sensor type "
|
44
|
+
"from the configuration for #{name}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/saal.gemspec
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
s.rubygems_version = '1.3.5'
|
5
|
+
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
|
8
|
+
s.name = 'saal'
|
9
|
+
s.version = '0.2.2'
|
10
|
+
s.date = '2010-12-29'
|
11
|
+
|
12
|
+
s.summary = "Thin abstraction layer for interfacing and recording sensors (currently onewire) and actuators (currently dinrelay)"
|
13
|
+
s.description = <<EOF
|
14
|
+
A daemon and libraries to create an abstraction layer that interfaces with
|
15
|
+
sensors and actuators, recording their state, responding to requests
|
16
|
+
for current and historical values, and allowing changes of state.
|
17
|
+
EOF
|
18
|
+
|
19
|
+
s.authors = ["Pedro Côrte-Real"]
|
20
|
+
s.email = 'pedro@pedrocr.net'
|
21
|
+
s.homepage = 'https://github.com/pedrocr/saal'
|
22
|
+
|
23
|
+
s.require_paths = %w[lib]
|
24
|
+
|
25
|
+
s.has_rdoc = true
|
26
|
+
s.rdoc_options = ['-S', '-w 2', '-N', '-c utf8']
|
27
|
+
s.extra_rdoc_files = %w[README.rdoc LICENSE]
|
28
|
+
|
29
|
+
s.executables = Dir.glob("bin/*").map{|f| f.gsub('bin/','')}
|
30
|
+
|
31
|
+
s.add_dependency('ownet', [">= 0.1.0"])
|
32
|
+
s.add_dependency('nokogiri')
|
33
|
+
s.add_dependency('mysql')
|
34
|
+
|
35
|
+
# = MANIFEST =
|
36
|
+
s.files = %w[
|
37
|
+
LICENSE
|
38
|
+
README.rdoc
|
39
|
+
Rakefile
|
40
|
+
TODO
|
41
|
+
bin/.gitignore
|
42
|
+
bin/dinrelayset
|
43
|
+
bin/dinrelaystatus
|
44
|
+
bin/saal_chart
|
45
|
+
bin/saal_daemon
|
46
|
+
bin/saal_dump_database
|
47
|
+
bin/saal_import_mysql
|
48
|
+
bin/saal_readall
|
49
|
+
lib/chart_data.rb
|
50
|
+
lib/daemon.rb
|
51
|
+
lib/dbstore.rb
|
52
|
+
lib/dinrelay.rb
|
53
|
+
lib/outliercache.rb
|
54
|
+
lib/owsensor.rb
|
55
|
+
lib/saal.rb
|
56
|
+
lib/sensor.rb
|
57
|
+
lib/sensors.rb
|
58
|
+
saal.gemspec
|
59
|
+
test/chart_data_test.rb
|
60
|
+
test/daemon_test.rb
|
61
|
+
test/dbstore_test.rb
|
62
|
+
test/dinrelay.html.erb
|
63
|
+
test/dinrelay_test.rb
|
64
|
+
test/nonexistant_sensor.yml
|
65
|
+
test/outliercache_test.rb
|
66
|
+
test/sensor_test.rb
|
67
|
+
test/sensors_test.rb
|
68
|
+
test/test_db.yml
|
69
|
+
test/test_dinrelay_sensors.yml
|
70
|
+
test/test_helper.rb
|
71
|
+
test/test_sensor_cleanups.yml
|
72
|
+
test/test_sensors.yml
|
73
|
+
]
|
74
|
+
# = MANIFEST =
|
75
|
+
|
76
|
+
s.test_files = s.files.select { |path| path =~ /^test\/.*\.rb/ }
|
77
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
MOCK_AVERAGES = [40.001,30.002,nil,60.004,300.005]
|
5
|
+
MOCK_MAX = 215.3
|
6
|
+
MOCK_MIN = 35.2
|
7
|
+
NORMALIZED_MOCK_AVERAGES = [2.7,0.0,-1.0,13.8,100.0]
|
8
|
+
|
9
|
+
class MockSensor
|
10
|
+
attr_reader :asked_averages
|
11
|
+
def initialize
|
12
|
+
@averages = MOCK_AVERAGES.dup
|
13
|
+
@asked_averages = []
|
14
|
+
end
|
15
|
+
def average(from, to)
|
16
|
+
@asked_averages << [from,to];
|
17
|
+
@averages.shift
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TestChartData < Test::Unit::TestCase
|
22
|
+
def test_get_data
|
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]],
|
27
|
+
sensor.asked_averages)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_normalize_data
|
31
|
+
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)
|
38
|
+
end
|
39
|
+
end
|
data/test/daemon_test.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
class TestDaemon < Test::Unit::TestCase
|
5
|
+
def test_working_daemon
|
6
|
+
db_setup
|
7
|
+
nsecs = 0.5
|
8
|
+
interval = 0.00001
|
9
|
+
with_fake_owserver do
|
10
|
+
d = SAAL::Daemon.new(:keep_stdin => true,
|
11
|
+
:interval => interval,
|
12
|
+
:dbconf => TEST_DBCONF,
|
13
|
+
:sensorconf => TEST_SENSORS_FILE)
|
14
|
+
pid = d.run
|
15
|
+
sleep nsecs # Potential timing bug when the system is under load
|
16
|
+
Process.kill("TERM", pid)
|
17
|
+
Process.waitpid(pid)
|
18
|
+
end
|
19
|
+
|
20
|
+
db_test_query("SELECT * FROM sensor_reads") do |res|
|
21
|
+
assert res.num_rows > 0, "No sensor reads in DB"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_empty_reads_daemon
|
26
|
+
db_setup
|
27
|
+
nsecs = 0.5
|
28
|
+
interval = 0.00001
|
29
|
+
with_fake_owserver do
|
30
|
+
d = SAAL::Daemon.new(:keep_stdin => true,
|
31
|
+
:interval => interval,
|
32
|
+
:dbconf => TEST_DBCONF,
|
33
|
+
:sensorconf => TEST_NONEXIST_SENSOR_FILE)
|
34
|
+
pid = d.run
|
35
|
+
sleep nsecs # Potential timing bug when the system is under load
|
36
|
+
Process.kill("TERM", pid)
|
37
|
+
Process.waitpid(pid)
|
38
|
+
end
|
39
|
+
|
40
|
+
db_test_query("SELECT * FROM sensor_reads") do |res|
|
41
|
+
assert res.num_rows == 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper.rb'
|
2
|
+
|
3
|
+
class TestFileStore < Test::Unit::TestCase
|
4
|
+
def test_insert
|
5
|
+
db_setup
|
6
|
+
test_time = 1196024160
|
7
|
+
test_value = 7.323
|
8
|
+
|
9
|
+
@dbstore.write(:test_sensor, test_time, test_value)
|
10
|
+
|
11
|
+
db_test_query("SELECT * FROM sensor_reads") do |res|
|
12
|
+
assert_equal 1, res.num_rows
|
13
|
+
assert_equal ["test_sensor", test_time.to_s, test_value.to_s], res.fetch_row
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_insert_nil
|
18
|
+
db_setup
|
19
|
+
|
20
|
+
assert_raise(ArgumentError) {@dbstore.write(:test_sensor, 1, nil)}
|
21
|
+
assert_raise(ArgumentError) {@dbstore.write(:test_sensor, 0, 1)}
|
22
|
+
assert_raise(ArgumentError) {@dbstore.write(:test_sensor, nil, 1)}
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_average
|
26
|
+
db_setup
|
27
|
+
test_values = [[10, 7.323],[12, 5.432],[23, -2.125], [44, 0.123]]
|
28
|
+
test_average = (5.432 - 2.125)/2.0
|
29
|
+
test_values.each do |values|
|
30
|
+
@dbstore.write(:test_sensor, *values)
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_instance_of Float, @dbstore.average(:test_sensor, 11, 25)
|
34
|
+
assert_in_delta test_average, @dbstore.average(:test_sensor, 11, 25), 0.001
|
35
|
+
assert_in_delta test_average, @dbstore.average(:test_sensor, 12, 25), 0.001
|
36
|
+
assert_in_delta test_average, @dbstore.average(:test_sensor, 12, 23), 0.001
|
37
|
+
|
38
|
+
# when there are no points it's nil
|
39
|
+
assert_nil @dbstore.average(:test_sensor, 50, 60)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_min_max
|
43
|
+
db_setup
|
44
|
+
test_values = [[10, 7.323],[12, 5.432],[23, -2.125], [44, 0.123]]
|
45
|
+
test_values.each do |values|
|
46
|
+
@dbstore.write(:test_sensor, *values)
|
47
|
+
end
|
48
|
+
|
49
|
+
[[:minimum, -2.125], [:maximum, 5.432]].each do |func, value|
|
50
|
+
assert_instance_of Float, @dbstore.send(func, :test_sensor, 11, 25)
|
51
|
+
assert_in_delta value, @dbstore.send(func,:test_sensor, 11, 25), 0.0001
|
52
|
+
assert_in_delta value, @dbstore.send(func,:test_sensor, 12, 25), 0.0001
|
53
|
+
assert_in_delta value, @dbstore.send(func,:test_sensor, 12, 23), 0.0001
|
54
|
+
|
55
|
+
# when there are no points it's nil
|
56
|
+
assert_nil @dbstore.send(func,:test_sensor, 50, 60)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_enumerable
|
61
|
+
db_setup
|
62
|
+
test_time = 1196024160
|
63
|
+
test_value = 7.323
|
64
|
+
n = 5
|
65
|
+
|
66
|
+
n.times {@dbstore.write(:test_sensor, test_time, test_value)}
|
67
|
+
assert_equal [["test_sensor", test_time, test_value]]*n,
|
68
|
+
@dbstore.map{|sensor,time,value| [sensor,time,value]}
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-15">
|
2
|
+
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
|
3
|
+
|
4
|
+
<meta http-equiv="Refresh" content="300">
|
5
|
+
<title>Outlet Control - Lite Power Controller</title></head>
|
6
|
+
<!-- state=00 lock=00 -->
|
7
|
+
|
8
|
+
<body alink="#0000FF" vlink="#0000FF">
|
9
|
+
<font face="Arial, Helvetica, Sans-Serif">
|
10
|
+
<table width="100%" cellspacing="0" cellpadding="0">
|
11
|
+
<tbody><tr>
|
12
|
+
<td valign="top" width="17%" height="100%">
|
13
|
+
|
14
|
+
<!-- menu -->
|
15
|
+
<table width="100%" height="100%" align="center" border="0" cellspacing="1" cellpadding="0">
|
16
|
+
<tbody><tr><td valign="top" bgcolor="#F4F4F4">
|
17
|
+
<table width="100%" cellpadding="1" cellspacing="5">
|
18
|
+
|
19
|
+
<tbody><tr><td align="center">
|
20
|
+
|
21
|
+
<table><tbody><tr><td><a href="http://www.digital-loggers.com/8.html"><img src="./index_files/logo.gif" width="195" height="65" border="0"></a></td>
|
22
|
+
|
23
|
+
<td><b><font size="-1">Ethernet Power Controller</font></b></td></tr></tbody></table>
|
24
|
+
<hr>
|
25
|
+
</td></tr>
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
<tr><td nowrap=""><b><a href="./index_files/index.html">Outlet Control</a></b></td></tr>
|
30
|
+
<tr><td nowrap=""><b><a href="http://localhost:8080/admin.htm">Setup</a></b></td></tr>
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
<tr><td nowrap=""><b><a href="http://localhost:8080/ap.htm">AutoPing</a></b></td></tr>
|
37
|
+
<tr><td nowrap=""><b><a href="http://localhost:8080/syslog.htm">System Log</a></b></td></tr>
|
38
|
+
<tr><td nowrap=""><b><a href="http://localhost:8080/logout">Logout</a></b></td></tr>
|
39
|
+
<tr><td nowrap=""><b><a href="http://localhost:8080/help/">Help</a></b></td></tr>
|
40
|
+
|
41
|
+
<tr><td><hr></td></tr>
|
42
|
+
|
43
|
+
|
44
|
+
<tr><td><b><a href="http://www.digital-loggers.com/5.html">Manual</a></b></td></tr>
|
45
|
+
|
46
|
+
<tr><td><b><a href="http://www.digital-loggers.com/6.html">FAQ</a></b></td></tr>
|
47
|
+
|
48
|
+
<tr><td><b><a href="http://www.digital-loggers.com/7.html">Product Information</a></b></td></tr>
|
49
|
+
|
50
|
+
<tr><td><b><a href="http://www.digital-loggers.com/8.html">Digital Loggers, Inc.</a></b></td></tr>
|
51
|
+
|
52
|
+
|
53
|
+
</tbody></table>
|
54
|
+
</td></tr>
|
55
|
+
|
56
|
+
|
57
|
+
<tr><td valign="bottom" height="100%" bgcolor="#F4F4F4">
|
58
|
+
<small>Version 1.2.C (Apr 12 2009 / 14:28:13) 43858FBE-7E60D1A3</small>
|
59
|
+
</td></tr>
|
60
|
+
<tr><td valign="bottom" height="100%" bgcolor="#F4F4F4">
|
61
|
+
<small>S/N:0000131097</small>
|
62
|
+
</td></tr>
|
63
|
+
|
64
|
+
</tbody></table>
|
65
|
+
<!-- /menu -->
|
66
|
+
|
67
|
+
</td>
|
68
|
+
<td valign="top" width="83%">
|
69
|
+
|
70
|
+
<!-- heading table -->
|
71
|
+
<table width="100%" align="center" border="0" cellspacing="1" cellpadding="3">
|
72
|
+
<tbody><tr><td bgcolor="red"> </td></tr><tr><td align="center"><h1>We recommend that you change the password!</h1></td></tr><tr><td bgcolor="red"> </td></tr>
|
73
|
+
<tr>
|
74
|
+
<th bgcolor="#DDDDFF" align="left">
|
75
|
+
Controller: Lite Power Controller
|
76
|
+
</th>
|
77
|
+
</tr>
|
78
|
+
|
79
|
+
<tr bgcolor="#FFFFFF" align="left">
|
80
|
+
<td>
|
81
|
+
Uptime: 115:48:32 <!-- 416912s up -->
|
82
|
+
</td>
|
83
|
+
</tr>
|
84
|
+
|
85
|
+
</tbody></table>
|
86
|
+
<!-- /heading table -->
|
87
|
+
|
88
|
+
<br>
|
89
|
+
|
90
|
+
<!-- individual control table -->
|
91
|
+
<table width="100%" align="center" border="0" cellspacing="1" cellpadding="3">
|
92
|
+
|
93
|
+
<tbody><tr>
|
94
|
+
<td bgcolor="#DDDDFF" colspan="5" align="left">
|
95
|
+
Individual Control
|
96
|
+
</td>
|
97
|
+
</tr>
|
98
|
+
|
99
|
+
<!-- heading rows -->
|
100
|
+
<tr bgcolor="#DDDDDD">
|
101
|
+
<th>#</th>
|
102
|
+
<th align="left">Name</th>
|
103
|
+
<th align="left">State</th>
|
104
|
+
<th align="left" colspan="2">Action</th>
|
105
|
+
</tr>
|
106
|
+
<!-- /heading rows -->
|
107
|
+
|
108
|
+
<% (1..8).each do |num| %>
|
109
|
+
<% state = outlets[num] %>
|
110
|
+
<% color = state == "ON" ? "green" : "red" %>
|
111
|
+
<% reverse = state == "ON" ? "OFF" : "ON" %>
|
112
|
+
<tr bgcolor="#F4F4F4"><td align="center">8</td>
|
113
|
+
<td>Outlet <%= num %></td><td>
|
114
|
+
<b><font color="<%= color %>"><%= state %></font></b></td><td>
|
115
|
+
<a href="http://example.com/outlet?<%= num %>=<%= reverse %>">Switch <%= reverse %></a>
|
116
|
+
</td><td>
|
117
|
+
<!-- <a href=outlet?<%= num %>=CCL>Cycle</a> -->
|
118
|
+
</td></tr>
|
119
|
+
<% end %>>
|
120
|
+
|
121
|
+
</tbody></table>
|
122
|
+
<!-- /individual control table -->
|
123
|
+
|
124
|
+
<br>
|
125
|
+
|
126
|
+
<table width="100%" align="center" border="0" cellspacing="1" cellpadding="3">
|
127
|
+
<tbody><tr><td bgcolor="#DDDDFF" align="left">Master Control</td></tr>
|
128
|
+
|
129
|
+
<tr><td bgcolor="#F4F4F4" align="left"><a href="http://localhost:8080/outlet?a=OFF">All outlets OFF</a></td></tr>
|
130
|
+
<tr><td bgcolor="#F4F4F4" align="left"><a href="http://localhost:8080/outlet?a=ON">All outlets ON</a></td></tr>
|
131
|
+
<tr><td bgcolor="#F4F4F4" align="left"><a href="http://localhost:8080/outlet?a=CCL">Cycle all outlets</a></td></tr>
|
132
|
+
<tr><td align="center">Sequence delay: 1 sec.</td></tr>
|
133
|
+
|
134
|
+
</tbody></table>
|
135
|
+
|
136
|
+
|
137
|
+
</td>
|
138
|
+
</tr>
|
139
|
+
</tbody></table>
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
</font></body><style type="text/css">embed[type*="application/x-shockwave-flash"],embed[src*=".swf"],object[type*="application/x-shockwave-flash"],object[codetype*="application/x-shockwave-flash"],object[src*=".swf"],object[codebase*="swflash.cab"],object[classid*="D27CDB6E-AE6D-11cf-96B8-444553540000"],object[classid*="d27cdb6e-ae6d-11cf-96b8-444553540000"],object[classid*="D27CDB6E-AE6D-11cf-96B8-444553540000"]{ display: none !important;}</style></html>
|