saal 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,160 @@
1
+ # Based on the jekyll Rakefile (http://github.com/mojombo/jekyll)
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'date'
6
+ require 'rcov/rcovtask'
7
+ require 'rake/testtask'
8
+ require 'rake/rdoctask'
9
+
10
+ #############################################################################
11
+ #
12
+ # Helper functions
13
+ #
14
+ #############################################################################
15
+
16
+ def name
17
+ @name ||= Dir['*.gemspec'].first.split('.').first
18
+ end
19
+
20
+ def version
21
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
22
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
23
+ end
24
+
25
+ def date
26
+ Date.today.to_s
27
+ end
28
+
29
+ def rubyforge_project
30
+ name
31
+ end
32
+
33
+ def gemspec_file
34
+ "#{name}.gemspec"
35
+ end
36
+
37
+ def gem_file
38
+ "#{name}-#{version}.gem"
39
+ end
40
+
41
+ def replace_header(head, header_name)
42
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
43
+ end
44
+
45
+ #############################################################################
46
+ #
47
+ # Standard tasks
48
+ #
49
+ #############################################################################
50
+
51
+ task :default => [:test]
52
+
53
+ Rake::TestTask.new(:test) do |test|
54
+ test.libs << 'lib' << 'test'
55
+ test.pattern = 'test/*_test.rb'
56
+ test.verbose = true
57
+ end
58
+
59
+ Rcov::RcovTask.new(:coverage) do |t|
60
+ t.libs << "test"
61
+ t.test_files = FileList['test/*_test.rb']
62
+ t.rcov_opts << ['--exclude "^/"', '--include "lib/.*\.rb"']
63
+ t.output_dir = 'coverage'
64
+ t.verbose = true
65
+ end
66
+
67
+ Rake::RDocTask.new do |rdoc|
68
+ rdoc.rdoc_dir = 'rdoc'
69
+ rdoc.title = "#{name} #{version}"
70
+ rdoc.rdoc_files.include('README*')
71
+ rdoc.rdoc_files.include('lib/**/*.rb')
72
+ end
73
+
74
+ desc "Open an irb session preloaded with this library"
75
+ task :console do
76
+ sh "irb -rubygems -r ./lib/#{name}.rb"
77
+ end
78
+
79
+ desc "Prints code to test ratio stats"
80
+ task :stats do
81
+ CODE_FILES = "lib/**/*.rb"
82
+ TEST_FILES = "test/*_test.rb"
83
+
84
+ code_code, code_comments = count_lines(FileList[CODE_FILES])
85
+ test_code, test_comments = count_lines(FileList[TEST_FILES])
86
+
87
+ puts "Code lines: #{code_code} code, #{code_comments} comments"
88
+ puts "Test lines: #{test_code} code, #{test_comments} comments"
89
+
90
+ ratio = test_code.to_f/code_code.to_f
91
+
92
+ puts "Code to test ratio: 1:%.2f" % ratio
93
+ end
94
+
95
+ def count_lines(files)
96
+ code = 0
97
+ comments = 0
98
+ files.each do |f|
99
+ File.open(f).each do |line|
100
+ if line.strip[0] == '#'[0]
101
+ comments += 1
102
+ else
103
+ code += 1
104
+ end
105
+ end
106
+ end
107
+ [code, comments]
108
+ end
109
+
110
+ #############################################################################
111
+ #
112
+ # Packaging tasks
113
+ #
114
+ #############################################################################
115
+
116
+ desc "git tag, build and release gem"
117
+ task :release => :build do
118
+ unless `git branch` =~ /^\* master$/
119
+ puts "You must be on the master branch to release!"
120
+ exit!
121
+ end
122
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
123
+ sh "git tag v#{version}"
124
+ sh "git push origin master"
125
+ sh "git push origin v#{version}"
126
+ sh "gem push pkg/#{name}-#{version}.gem"
127
+ end
128
+
129
+ desc "Build gem"
130
+ task :build => :gemspec do
131
+ sh "mkdir -p pkg"
132
+ sh "gem build #{gemspec_file}"
133
+ sh "mv #{gem_file} pkg"
134
+ end
135
+
136
+ task :gemspec do
137
+ # read spec file and split out manifest section
138
+ spec = File.read(gemspec_file)
139
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
140
+
141
+ # replace name version and date
142
+ replace_header(head, :name)
143
+ replace_header(head, :version)
144
+ replace_header(head, :date)
145
+
146
+ # determine file list from git ls-files
147
+ files = `git ls-files`.
148
+ split("\n").
149
+ sort.
150
+ reject { |file| file =~ /^\./ }.
151
+ reject { |file| file =~ /^(rdoc|pkg|coverage)/ }.
152
+ map { |file| " #{file}" }.
153
+ join("\n")
154
+
155
+ # piece file back together and write
156
+ manifest = " s.files = %w[\n#{files}\n ]\n"
157
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
158
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
159
+ puts "Updated #{gemspec_file}"
160
+ end
data/TODO ADDED
@@ -0,0 +1,24 @@
1
+ TODO
2
+ - Add logging to the daemon
3
+ ?- Change the sensor configuration to be a ruby DSL and make it a daemon config
4
+ - Split classes into one per file with corresponding test (rails style)
5
+ - Verify inputs on the server to make sure it never crashes
6
+ - Remove the chart configuration options from the saal_chart script
7
+ - Add an init.d file to the package (and possibly an installer script for ubuntu/debian)
8
+ - Add outlier detection and removal
9
+ DONE
10
+ X- Make the server bind only to a certain interface (not applicable)
11
+ X- Override OWNet::Connection with a mock object so that owserver is not needed
12
+ X- Implement GET_ALL
13
+ X- Multithread the server
14
+ X- Make connections persistant
15
+ - Find a better way to handle stdin, stdout, stderr for tests (an option maybe)
16
+ - Implement the charts
17
+ - Write client library for server part of daemon
18
+ - Implement AVERAGE
19
+ - Make sensor reads uncached
20
+ - Refactor daemon.rb to pull out its daemon creating code from the process
21
+ - Create a client/server framework to ask the server for values
22
+ - Make the date returned by GET the date of the last read (GET doesn't return
23
+ a date now
24
+ - Implement monthly, yearly and 10-day average charts
data/bin/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ sensor_reads_ulisses.db
2
+ sensors_ulisses.yml
data/bin/dinrelayset ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
4
+
5
+ def exit_usage
6
+ $stderr.puts "Usage: dinrelayset <host:[port]> <port 1-8> <ON|OFF> [user:password]"
7
+ exit 2
8
+ end
9
+
10
+ if ARGV.size > 4 || ARGV.size < 3
11
+ exit_usage
12
+ end
13
+
14
+ opts = {}
15
+
16
+ hp = ARGV[0].split(":")
17
+ opts[:host] = hp[0]
18
+ opts[:port] = (hp[1] || 80).to_i
19
+
20
+ num = ARGV[1].to_i
21
+ exit_usage if num < 1 || num > 8
22
+ state = ARGV[2]
23
+ exit_usage if state != 'ON' && state != 'OFF'
24
+
25
+ if ARGV[4]
26
+ up = ARGV[4].split(":")
27
+ exit_usage if up.size != 2
28
+ opts[:user] = up[0]
29
+ opts[:pass] = up[1]
30
+ end
31
+
32
+ puts "Setting outlet #{num} of #{opts[:host]}:#{opts[:port]} to #{state}"
33
+ og = SAAL::DINRelay::OutletGroup.new(opts)
34
+ og.set_state(num, state)
35
+
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
4
+
5
+ def exit_usage
6
+ $stderr.puts "Usage: dinrelaystatus <host:[port]> [user:password]"
7
+ exit 2
8
+ end
9
+
10
+ if ARGV.size > 2 || ARGV.size < 1
11
+ exit_usage
12
+ end
13
+
14
+ opts = {}
15
+
16
+ hp = ARGV[0].split(":")
17
+ opts[:host] = hp[0]
18
+ opts[:port] = (hp[1] || 80).to_i
19
+
20
+ if ARGV[1]
21
+ up = ARGV[1].split(":")
22
+ exit_usage if up.size != 2
23
+ opts[:user] = up[0]
24
+ opts[:pass] = up[1]
25
+ end
26
+
27
+ puts "Checking outlets for #{opts[:host]}:#{opts[:port]}"
28
+ og = SAAL::DINRelay::OutletGroup.new(opts)
29
+ (1..8).each do |num|
30
+ puts "Outlet \##{num}: #{og.state(num).to_s}"
31
+ end
32
+
data/bin/saal_chart ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
4
+
5
+ def usage
6
+ $stderr.puts("Usage: saal_chart <day|week|4week|year|4year> <chart_file.png>")
7
+ end
8
+
9
+ if ARGV.size != 2
10
+ usage
11
+ exit (2)
12
+ end
13
+
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"
93
+ 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 ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ SENSOR_INTERVAL = 60 # seconds between consecutive measurements of the sensors
4
+ DBCONF = "/etc/saal/database.yml"
5
+ SENSORSCONF = "/etc/saal/sensors.yml"
6
+
7
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
8
+
9
+ if ARGV.size != 1
10
+ $stderr.puts "Usage: saal_daemon [pidfile]"
11
+ exit 2
12
+ else
13
+ pidfile = ARGV[0]
14
+ d = SAAL::Daemon.new(:interval => SENSOR_INTERVAL,
15
+ :sensorconf => SENSORSCONF,
16
+ :dbconf => DBCONF)
17
+ pid = d.run
18
+ File.open(pidfile, 'w') do |f|
19
+ f.write(pid)
20
+ f.close
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
4
+
5
+ if ARGV.size != 0
6
+ puts "Usage: saal_dump_database"
7
+ exit 2
8
+ else
9
+ dbstore = SAAL::DBStore.new
10
+ dbstore.each {|sensor,date,value| puts "#{sensor} #{date} #{value}"}
11
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
4
+
5
+ begin
6
+ dbstore = SAAL::DBStore.new
7
+
8
+ nrows = 0
9
+ while not $stdin.eof?
10
+ values = $stdin.readline.split(" ").map{|s| s.strip}
11
+ if values[2] != ""
12
+ dbstore.write(values[0], values[1].to_i, values[2].to_f)
13
+ nrows += 1
14
+ end
15
+ end
16
+ puts "Number of rows inserted: #{nrows}"
17
+ end
18
+
data/bin/saal_readall ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__)+'/../lib/saal.rb'
4
+
5
+ if ARGV.size > 0
6
+ $stderr.puts "saal_readall takes no arguments"
7
+ exit 2
8
+ else
9
+ SAAL::Sensors.new.each{|s| puts "#{s.name}: #{s.read.to_s}"}
10
+ end
data/lib/chart_data.rb ADDED
@@ -0,0 +1,30 @@
1
+ module SAAL
2
+ class ChartData
3
+ def initialize(sensor)
4
+ @sensor = sensor
5
+ end
6
+
7
+ def get_data(from, to, num)
8
+ step = (to - from).to_f/num.to_f
9
+ (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)
14
+ 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
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/daemon.rb ADDED
@@ -0,0 +1,63 @@
1
+ module SAAL
2
+ class ForkedRunner
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
10
+ yield ForkedRunner.new
11
+ end
12
+ end
13
+
14
+ def initialize
15
+ @rd, @wr = IO.pipe
16
+ @stop = false
17
+ trap_signals
18
+ end
19
+
20
+ def trap_signals
21
+ Signal.trap("TERM") {do_exit}
22
+ Signal.trap("INT") {do_exit}
23
+ end
24
+
25
+ def do_exit
26
+ @stop = true
27
+ @wr.write(1)
28
+ end
29
+
30
+ def stop?
31
+ @stop
32
+ end
33
+
34
+ def sleep(time)
35
+ select([],[],[],time)
36
+ end
37
+
38
+ def select(read, write=[], err=[], time=nil)
39
+ if time
40
+ Kernel.select(read+[@rd],write,err,time)
41
+ else
42
+ Kernel.select(read+[@rd],write,err)
43
+ end
44
+ end
45
+ end
46
+
47
+ class Daemon
48
+ def initialize(opts={})
49
+ @opts = opts
50
+ end
51
+
52
+ def run
53
+ ForkedRunner.run_as_fork(@opts) do |forked_runner|
54
+ @sensors = SAAL::Sensors.new(@opts[:sensorconf], @opts[:dbconf])
55
+ @interval = @opts[:interval] || 60
56
+ begin
57
+ @sensors.each {|sensor| sensor.store_value}
58
+ forked_runner.sleep @interval
59
+ end while !forked_runner.stop?
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/dbstore.rb ADDED
@@ -0,0 +1,86 @@
1
+ module SAAL
2
+ class DBStore
3
+ include Enumerable
4
+ def initialize(conffile=SAAL::DBCONF)
5
+ @dbopts = YAML::load(File.new(conffile))
6
+ @db = nil
7
+ db_initialize
8
+ end
9
+
10
+ def db_initialize
11
+ db_query "CREATE TABLE IF NOT EXISTS sensor_reads
12
+ (sensor VARCHAR(100),
13
+ date INT,
14
+ value FLOAT,
15
+ INDEX USING HASH (sensor),
16
+ INDEX USING BTREE (date))"
17
+ end
18
+
19
+ def db_wipe
20
+ db_query "DROP TABLE sensor_reads"
21
+ end
22
+
23
+ def write(sensor, date, value)
24
+ raise ArgumentError, "Trying to store an empty sensor read" if !value
25
+ raise ArgumentError, "Trying to store an empty timestamp" if !date
26
+ raise ArgumentError, "Trying to store a timestamp <= 0" if date <= 0
27
+ db_query "INSERT INTO sensor_reads VALUES
28
+ ('"+db_quote(sensor.to_s)+"',"+date.to_s+","+value.to_s+")"
29
+ end
30
+
31
+ def average(sensor, from, to)
32
+ db_range("AVG", sensor, from, to)
33
+ end
34
+
35
+ def minimum(sensor, from, to)
36
+ db_range("MIN", sensor, from, to)
37
+ end
38
+ def maximum(sensor, from, to)
39
+ db_range("MAX", sensor, from, to)
40
+ end
41
+
42
+ def each
43
+ db_query "SELECT sensor,date,value FROM sensor_reads" do |r|
44
+ r.num_rows.times do
45
+ row = r.fetch_row
46
+ yield [row[0],row[1].to_i, row[2].to_f]
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+ def db_range(function, sensor, from, to)
53
+ db_query "SELECT #{function}(value) AS average FROM sensor_reads
54
+ WHERE sensor = '#{db_quote(sensor.to_s)}'
55
+ AND date >= #{from.to_s}
56
+ AND date <= #{to.to_s}" do |r|
57
+ if r.num_rows == 0
58
+ nil
59
+ else
60
+ row = r.fetch_row
61
+ row[0] ? row[0].to_f : nil
62
+ end
63
+ end
64
+ end
65
+
66
+ def db_quote(text)
67
+ Mysql.quote(text)
68
+ end
69
+
70
+ def db_query(query)
71
+ db = nil
72
+ begin
73
+ # connect to the MySQL server
74
+ db = Mysql.new(@dbopts['host'],@dbopts['user'],@dbopts['pass'],
75
+ @dbopts['db'],@dbopts['port'],@dbopts['socket'],
76
+ @dbopts['flags'])
77
+ res = db.query(query)
78
+ yield res if block_given?
79
+ rescue Mysql::Error => e
80
+ $stderr.puts "MySQL Error \#{e.errno}: \#{e.error}"
81
+ ensure
82
+ db.close if db
83
+ end
84
+ end
85
+ end
86
+ end
data/lib/dinrelay.rb ADDED
@@ -0,0 +1,67 @@
1
+ require 'net/http'
2
+
3
+ module SAAL
4
+ module DINRelay
5
+ class Outlet < SensorUnderlying
6
+ writeable!
7
+
8
+ def initialize(num, outletgroup)
9
+ @num = num
10
+ @og = outletgroup
11
+ end
12
+
13
+ def read(uncached = false)
14
+ {'ON' => 1.0, 'OFF' => 0.0}[@og.state(@num)]
15
+ end
16
+
17
+ def write(value)
18
+ newstate = {1.0 => 'ON', 0.0 => 'OFF'}[value]
19
+ if newstate
20
+ @og.set_state(@num,newstate)
21
+ value
22
+ end
23
+ end
24
+ end
25
+
26
+ class OutletGroup
27
+ def initialize(opts={})
28
+ @host = opts[:host] || opts['host'] || 'localhost'
29
+ @port = opts[:port] || opts['port'] || 80
30
+ @user = opts[:user] || opts['user'] || 'admin'
31
+ @pass = opts[:pass] || opts['pass'] || '1234'
32
+ end
33
+
34
+ def state(num)
35
+ response = do_get('/index.htm')
36
+ return parse_index_html(response.body)[num]
37
+ end
38
+
39
+ def set_state(num, state)
40
+ response = do_get("/outlet?#{num}=#{state}")
41
+ response.code == "200"
42
+ end
43
+
44
+ private
45
+ 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
53
+ end
54
+ return response
55
+ end
56
+ end
57
+
58
+ def parse_index_html(str)
59
+ doc = Nokogiri::HTML(str)
60
+ outlets = doc.css('tr[bgcolor="#F4F4F4"]')
61
+ Hash[*((outlets.enum_for(:each_with_index).map do |el, index|
62
+ [index+1, el.css('font')[0].content]
63
+ end).flatten)]
64
+ end
65
+ end
66
+ end
67
+ end