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