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