saal 0.2.24 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/saal_chart +49 -4
- data/bin/saal_denkovi_relays +34 -0
- data/bin/saal_envoy_generate_config +53 -0
- data/bin/saal_envoy_read +144 -0
- data/lib/chart.rb +1 -1
- data/lib/chart_data.rb +1 -1
- data/lib/dbstore.rb +15 -18
- data/lib/denkovi.rb +101 -0
- data/lib/dinrelay.rb +2 -18
- data/lib/envoy.rb +287 -0
- data/lib/http.rb +51 -0
- data/lib/outliercache.rb +1 -1
- data/lib/saal.rb +5 -2
- data/lib/sensors.rb +31 -2
- data/saal.gemspec +16 -5
- data/test/chart_data_test.rb +1 -1
- data/test/chart_test.rb +0 -3
- data/test/daemon_test.rb +2 -2
- data/test/dbstore_test.rb +5 -2
- data/test/denkovi.json.erb +39 -0
- data/test/denkovi_test.rb +212 -0
- data/test/dinrelay_test.rb +1 -1
- data/test/sensors_test.rb +5 -5
- data/test/test_db.yml +3 -3
- data/test/test_denkovi_sensors.yml +39 -0
- data/test/test_helper.rb +2 -2
- data/test/test_sensors.yml +1 -1
- metadata +72 -42
- data/bin/saal_chart~ +0 -90
- data/bin/saal_daemon~ +0 -31
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 90aa53b1093427163af28bee630532e79ccefea47de44463641c3e387f038640
|
4
|
+
data.tar.gz: 41fca314cb32d91ca10bfb1982cf7e60a6b584c3c521b8ddcd148b18f465fd86
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff26de3ff3cca4750a365b9672a82165d449f7c11fb3bd57d7f3f27758636a521d766ae99dc84b50cd30e940453eb30826ad485e44f9e50d723bb5625cd07915
|
7
|
+
data.tar.gz: f93e18eacc3a2eb6555c95b9ea91f74506a39af91c6083ab6dbb3acc1ef2d83387db9ed59a573afcb7bbd1354cfdf7af51ec11122d9f48f287baf273c235debf
|
data/bin/saal_chart
CHANGED
@@ -16,7 +16,11 @@ if ARGV.size != 1
|
|
16
16
|
exit (2)
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
MAX_RANGES = {:temperature=>[-10,70],
|
20
|
+
:humidity=>[0,110],
|
21
|
+
:pressure=>[900,1200]}
|
22
|
+
|
23
|
+
TYPES = {:temperature => "ºC", :humidity => "%", :pressure => "hPa"}
|
20
24
|
|
21
25
|
|
22
26
|
SAAL::Charts.new.each do |chart|
|
@@ -57,9 +61,35 @@ SAAL::Charts.new.each do |chart|
|
|
57
61
|
NUM_VALUES_SMALL
|
58
62
|
|
59
63
|
@averages = chart.average(num_values)
|
64
|
+
@ranges = {}
|
65
|
+
|
66
|
+
# First find the smallest interval that fits all sensors for each type
|
67
|
+
@data = chart.sensors.map do |sensor|
|
68
|
+
avgs = @averages[sensor.name.to_sym]
|
69
|
+
|
70
|
+
min = avgs.select{|o| o != nil}.min
|
71
|
+
max = avgs.select{|o| o != nil}.max
|
72
|
+
if min and max
|
73
|
+
range = [(min/5).floor*5, (max/5).ceil*5]
|
74
|
+
@ranges[sensor.sensor_type] ||= range
|
75
|
+
previous = @ranges[sensor.sensor_type]
|
76
|
+
@ranges[sensor.sensor_type] = [[range[0],previous[0]].min,
|
77
|
+
[range[1],previous[1]].max]
|
78
|
+
end
|
79
|
+
end
|
60
80
|
|
81
|
+
# Then clip those intervals to MAX_RANGES
|
61
82
|
@data = chart.sensors.map do |sensor|
|
62
|
-
|
83
|
+
maxrange = MAX_RANGES[sensor.sensor_type]
|
84
|
+
@ranges[sensor.sensor_type] ||= maxrange
|
85
|
+
previous = @ranges[sensor.sensor_type]
|
86
|
+
@ranges[sensor.sensor_type] = [[maxrange[0],previous[0]].max,
|
87
|
+
[maxrange[1],previous[1]].min]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Finally use those intervals to normalize the data
|
91
|
+
@data = chart.sensors.map do |sensor|
|
92
|
+
normalize_data(@averages[sensor.name.to_sym], *@ranges[sensor.sensor_type])
|
63
93
|
end
|
64
94
|
|
65
95
|
@dataurl = @data.map {|values| values.join(",")}.join('|')
|
@@ -74,12 +104,27 @@ SAAL::Charts.new.each do |chart|
|
|
74
104
|
end
|
75
105
|
@xincr = 100.0/@numperiods.to_f*10000.truncate.to_f/10000
|
76
106
|
|
107
|
+
@axes = []
|
108
|
+
@ranges.each do |type, range|
|
109
|
+
min,max = range
|
110
|
+
step = (max-min).to_f/4.0
|
111
|
+
steps = (0..4).map{|i| (min+i*step).to_i.to_s}
|
112
|
+
steps[0] = "#{min} #{TYPES[type]}"
|
113
|
+
steps[4] = "#{max} #{TYPES[type]}"
|
114
|
+
@axes << steps.join("||")
|
115
|
+
end
|
116
|
+
# Duplicate the axis if there's only one
|
117
|
+
@axes *= 2 if @axes.size == 1
|
118
|
+
# Alternate between left and right axes
|
119
|
+
@axisset = (['y','r']*@axes.size)[0..@axes.size-1].join(",")
|
120
|
+
@axes = @axes.each_with_index.map {|a,i| "#{i+1}:|#{a}"}.join("|")
|
121
|
+
|
77
122
|
r[:chof] = "png"
|
78
123
|
r[:chs] = "700x300"
|
79
124
|
r[:cht] = "lc"
|
80
125
|
r[:chco] = "00ff00,ff0000,0000ff,ff9933,800080"
|
81
|
-
r[:chxt] = "x
|
82
|
-
r[:chxl] = "0:#{@periodnamesurl}
|
126
|
+
r[:chxt] = "x,#{@axisset}"
|
127
|
+
r[:chxl] = "0:#{@periodnamesurl}#{@axes}"
|
83
128
|
r[:chg] = "#{@xincr},12.5,1,5"
|
84
129
|
r[:chd] = "t:#{@dataurl}"
|
85
130
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__)+'/../lib/saal.rb'
|
4
|
+
|
5
|
+
def usage
|
6
|
+
$stderr.puts "USAGE: saal_denkovi_relays <host>"
|
7
|
+
$stderr.puts "USAGE: saal_denkovi_relays <host> <num> <ON/OFF>"
|
8
|
+
end
|
9
|
+
|
10
|
+
if ARGV.size != 1 && ARGV.size != 3
|
11
|
+
usage()
|
12
|
+
exit(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
denkovi = SAAL::Denkovi::OutletGroup::new(
|
16
|
+
:host => ARGV[0],
|
17
|
+
)
|
18
|
+
|
19
|
+
if ARGV.size == 3
|
20
|
+
num = ARGV[1].to_i
|
21
|
+
newstate = ARGV[2]
|
22
|
+
if !["ON","OFF"].include? newstate
|
23
|
+
$stderr.puts "ERROR: Unknown state '#{newstate}'"
|
24
|
+
usage()
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
puts "Setting Relay #{num} to #{newstate}"
|
28
|
+
denkovi.set_state(num, newstate)
|
29
|
+
end
|
30
|
+
|
31
|
+
(1..16).each do |num|
|
32
|
+
puts "Relay #{num} is #{denkovi.state(num)}"
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__)+'/../lib/saal.rb'
|
4
|
+
|
5
|
+
if ARGV.size != 1 && ARGV.size != 3
|
6
|
+
$stderr.puts "USAGE: saal_envoy_generate_config <host> [<user> <password>]"
|
7
|
+
exit(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
puts "power:"
|
11
|
+
puts " envoy_power_energy:"
|
12
|
+
puts " host: \"#{ARGV[0]}\""
|
13
|
+
puts
|
14
|
+
puts "ac:"
|
15
|
+
puts " envoy_ac_quality:"
|
16
|
+
puts " host: \"#{ARGV[0]}\""
|
17
|
+
|
18
|
+
user = "envoy"
|
19
|
+
password = nil
|
20
|
+
|
21
|
+
if ARGV[2]
|
22
|
+
user = ARGV[1]
|
23
|
+
password = ARGV[2]
|
24
|
+
else
|
25
|
+
Net::HTTP.get(ARGV[0], '/home').split("\n").each do |line|
|
26
|
+
if line.include?("serial:")
|
27
|
+
password = line.split('"')[1][-6..-1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
if !password
|
31
|
+
$stderr.puts "Couldn't find serial number for envoy"
|
32
|
+
exit 2
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
envoy = SAAL::Envoy::Inverters::new(
|
37
|
+
:host => ARGV[0],
|
38
|
+
:user => user,
|
39
|
+
:password => password,
|
40
|
+
)
|
41
|
+
envoy.set_all_inverters!
|
42
|
+
exit 0 if envoy.inverters.size <= 0
|
43
|
+
|
44
|
+
puts
|
45
|
+
puts "inverters:"
|
46
|
+
puts " envoy_inverters:"
|
47
|
+
puts " host: \"#{ARGV[0]}\""
|
48
|
+
puts " user: \"#{user}\""
|
49
|
+
puts " password: \"#{password}\""
|
50
|
+
puts " inverters:"
|
51
|
+
envoy.inverters.each do |serial|
|
52
|
+
puts " - #{serial}"
|
53
|
+
end
|
data/bin/saal_envoy_read
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__)+'/../lib/saal.rb'
|
4
|
+
|
5
|
+
if ARGV.size != 3
|
6
|
+
$stderr.puts "USAGE: saal_envoy_read <host> <user> <password>"
|
7
|
+
exit(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
def fdisp(val)
|
11
|
+
if val
|
12
|
+
'%10.0f' % val
|
13
|
+
else
|
14
|
+
" n/a"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def fdispk(val)
|
19
|
+
if val
|
20
|
+
'%10.2f' % (val / 1000.0)
|
21
|
+
else
|
22
|
+
" n/a"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def fdisp_dec(val)
|
27
|
+
if val
|
28
|
+
'%10.2f' % val
|
29
|
+
else
|
30
|
+
" n/a"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def l(vals, name)
|
35
|
+
sensor = vals[name]
|
36
|
+
if sensor
|
37
|
+
sensor.read
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def lratio(vals, name1, name2)
|
44
|
+
v1 = l(vals, name1)
|
45
|
+
v2 = l(vals, name2)
|
46
|
+
if v1 && v2
|
47
|
+
v1 / v2
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
ac_quality = SAAL::Envoy::ACQuality::new(:host => ARGV[0]).create_sensors
|
54
|
+
|
55
|
+
puts " ========= AC QUALITY ========"
|
56
|
+
puts " voltage (V) freq (Hz)"
|
57
|
+
def qual_line(vals,name, type)
|
58
|
+
puts " #{name} \
|
59
|
+
#{fdisp_dec(l(vals,"ac_#{type}_voltage"))} \
|
60
|
+
#{fdisp_dec(l(vals,"ac_#{type}_frequency"))} \
|
61
|
+
"
|
62
|
+
end
|
63
|
+
qual_line(ac_quality, "Total: ", "total")
|
64
|
+
qual_line(ac_quality, "Phase1:", "phase1")
|
65
|
+
qual_line(ac_quality, "Phase2:", "phase2")
|
66
|
+
qual_line(ac_quality, "Phase3:", "phase3")
|
67
|
+
|
68
|
+
production = SAAL::Envoy::PowerEnergy::new(:host => ARGV[0]).create_sensors
|
69
|
+
|
70
|
+
puts ""
|
71
|
+
puts " ============ TRUE POWER (W) ============ ======= TRUE ENERGY (kWh) ======="
|
72
|
+
puts " consumption production net consumption production net"
|
73
|
+
def p_line(vals, name, type, metric)
|
74
|
+
puts " #{name} \
|
75
|
+
#{fdisp(l(vals,"pv_total_consumption_#{type}_#{metric}_now"))} \
|
76
|
+
#{fdisp(l(vals,"pv_production_#{type}_#{metric}_now"))} \
|
77
|
+
#{fdisp(l(vals,"pv_net_consumption_#{type}_#{metric}_now"))} \
|
78
|
+
#{fdispk(l(vals,"pv_total_consumption_#{type}_#{metric}h_lifetime"))} \
|
79
|
+
#{fdispk(l(vals,"pv_production_#{type}_#{metric}h_lifetime"))} \
|
80
|
+
#{fdispk(l(vals,"pv_net_consumption_#{type}_#{metric}h_lifetime"))} \
|
81
|
+
"
|
82
|
+
end
|
83
|
+
p_line(production, "Total: ", "total", "w")
|
84
|
+
p_line(production, "Phase1:", "phase1", "w")
|
85
|
+
p_line(production, "Phase2:", "phase2", "w")
|
86
|
+
p_line(production, "Phase3:", "phase3", "w")
|
87
|
+
puts " Total Inverters: \
|
88
|
+
#{fdisp(l(production,"pv_production_inverters_w_now"))} \
|
89
|
+
\
|
90
|
+
#{fdispk(l(production,"pv_production_inverters_wh_lifetime"))} \
|
91
|
+
"
|
92
|
+
|
93
|
+
puts ""
|
94
|
+
puts " ========== APPARENT POWER (VA) ========= ===== APPARENT ENERGY (kVAh) ===="
|
95
|
+
puts " consumption production net consumption production net"
|
96
|
+
p_line(production, "Total: ", "total", "va")
|
97
|
+
p_line(production, "Phase1:", "phase1", "va")
|
98
|
+
p_line(production, "Phase2:", "phase2", "va")
|
99
|
+
p_line(production, "Phase3:", "phase3", "va")
|
100
|
+
|
101
|
+
def pf_line(vals, name, type, metric)
|
102
|
+
pf_total_consumption_instant = lratio(vals,"pv_total_consumption_#{type}_w_now","pv_total_consumption_#{type}_va_now")
|
103
|
+
pf_total_production_instant = lratio(vals,"pv_production_#{type}_w_now","pv_production_#{type}_va_now")
|
104
|
+
pf_net_production_instant = lratio(vals,"pv_net_consumption_#{type}_w_now","pv_net_consumption_#{type}_va_now")
|
105
|
+
|
106
|
+
pf_total_consumption_lifetime = lratio(vals,"pv_total_consumption_#{type}_wh_lifetime","pv_total_consumption_#{type}_vah_lifetime")
|
107
|
+
pf_total_production_lifetime = lratio(vals,"pv_production_#{type}_wh_lifetime","pv_production_#{type}_vah_lifetime")
|
108
|
+
pf_net_production_lifetime = lratio(vals,"pv_net_consumption_#{type}_wh_lifetime","pv_net_consumption_#{type}_vah_lifetime")
|
109
|
+
|
110
|
+
puts " #{name} \
|
111
|
+
#{fdisp_dec(pf_total_consumption_instant)} \
|
112
|
+
#{fdisp_dec(pf_total_production_instant)} \
|
113
|
+
#{fdisp_dec(pf_net_production_instant)} \
|
114
|
+
#{fdisp_dec(pf_total_consumption_lifetime)} \
|
115
|
+
#{fdisp_dec(pf_total_production_lifetime)} \
|
116
|
+
#{fdisp_dec(pf_net_production_lifetime)} \
|
117
|
+
"
|
118
|
+
end
|
119
|
+
|
120
|
+
puts ""
|
121
|
+
puts " ========= INSTANT POWER FACTOR ========= ====== LIFETIME POWER FACTOR ====="
|
122
|
+
puts " consumption production net consumption production net"
|
123
|
+
pf_line(production, "Total: ", "total", "va")
|
124
|
+
pf_line(production, "Phase1:", "phase1", "va")
|
125
|
+
pf_line(production, "Phase2:", "phase2", "va")
|
126
|
+
pf_line(production, "Phase3:", "phase3", "va")
|
127
|
+
|
128
|
+
puts ""
|
129
|
+
envoy = SAAL::Envoy::Inverters::new(
|
130
|
+
:host => ARGV[0],
|
131
|
+
:user => ARGV[1],
|
132
|
+
:password => ARGV[2],
|
133
|
+
:types => ["w_now", "last_report_date", "w_max"],
|
134
|
+
)
|
135
|
+
envoy.set_all_inverters!
|
136
|
+
inverters = envoy.create_sensors
|
137
|
+
puts " Found #{envoy.inverters.size} inverters"
|
138
|
+
envoy.inverters.each do |serial|
|
139
|
+
puts " INVERTER: #{serial} \
|
140
|
+
date:#{l(inverters,"inverters_#{serial}_last_report_date")} \
|
141
|
+
lastWatts:#{l(inverters,"inverters_#{serial}_w_now")} \
|
142
|
+
maxWatts:#{l(inverters,"inverters_#{serial}_w_max")} \
|
143
|
+
"
|
144
|
+
end
|
data/lib/chart.rb
CHANGED
@@ -5,7 +5,7 @@ module SAAL
|
|
5
5
|
@name = name
|
6
6
|
@defs = defs
|
7
7
|
@alignlabels = (defs['alignlabels'] || :center).to_sym
|
8
|
-
@sensors = defs['sensors'].map{|
|
8
|
+
@sensors = defs['sensors'].map{|sname| sensors.send(sname)}
|
9
9
|
@num = defs['last']
|
10
10
|
@periods = defs['periods']
|
11
11
|
@alt = defs['alt']
|
data/lib/chart_data.rb
CHANGED
data/lib/dbstore.rb
CHANGED
@@ -46,54 +46,51 @@ module SAAL
|
|
46
46
|
WHERE sensor = '#{db_quote(sensor.to_s)}'
|
47
47
|
AND date > '#{Time.now.utc.to_i - MAX_LAST_VAL_AGE}'
|
48
48
|
ORDER BY date DESC LIMIT 1" do |r|
|
49
|
-
|
50
|
-
|
49
|
+
row = r.first
|
50
|
+
if row
|
51
|
+
_date, value = [row["date"].to_i, row["value"].to_f]
|
52
|
+
value
|
51
53
|
else
|
52
|
-
|
53
|
-
date, value = [row[0].to_i, row[1].to_f]
|
54
|
-
return value
|
54
|
+
nil
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
59
|
def each
|
60
60
|
db_query "SELECT sensor,date,value FROM sensor_reads" do |r|
|
61
|
-
r.
|
62
|
-
row
|
63
|
-
yield [row[0],row[1].to_i, row[2].to_f]
|
61
|
+
r.each do |row|
|
62
|
+
yield [row["sensor"],row["date"].to_i, row["value"].to_f]
|
64
63
|
end
|
65
64
|
end
|
66
65
|
end
|
67
66
|
|
68
67
|
private
|
69
68
|
def db_range(function, sensor, from, to)
|
70
|
-
db_query "SELECT #{function}(value) AS
|
69
|
+
db_query "SELECT #{function}(value) AS func FROM sensor_reads
|
71
70
|
WHERE sensor = '#{db_quote(sensor.to_s)}'
|
72
71
|
AND date >= #{from.to_s}
|
73
72
|
AND date <= #{to.to_s}" do |r|
|
74
|
-
|
75
|
-
|
73
|
+
row = r.first
|
74
|
+
if row && row["func"]
|
75
|
+
row["func"].to_f
|
76
76
|
else
|
77
|
-
|
78
|
-
row[0] ? row[0].to_f : nil
|
77
|
+
nil
|
79
78
|
end
|
80
79
|
end
|
81
80
|
end
|
82
81
|
|
83
82
|
def db_quote(text)
|
84
|
-
|
83
|
+
Mysql2::Client.escape(text)
|
85
84
|
end
|
86
85
|
|
87
86
|
def db_query(query, opts={})
|
88
87
|
db = nil
|
89
88
|
begin
|
90
89
|
# connect to the MySQL server
|
91
|
-
db =
|
92
|
-
@dbopts['db'],@dbopts['port'],@dbopts['socket'],
|
93
|
-
@dbopts['flags'])
|
90
|
+
db = Mysql2::Client.new(@dbopts)
|
94
91
|
res = db.query(query)
|
95
92
|
yield res if block_given?
|
96
|
-
rescue
|
93
|
+
rescue Mysql2::Error => e
|
97
94
|
$stderr.puts "MySQL Error #{e.errno}: #{e.error}" if !(e.errno == opts[:ignoreerr])
|
98
95
|
ensure
|
99
96
|
db.close if db
|
data/lib/denkovi.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module SAAL
|
5
|
+
module Denkovi
|
6
|
+
class Outlet < SensorUnderlying
|
7
|
+
writeable!
|
8
|
+
|
9
|
+
def initialize(num, outletgroup)
|
10
|
+
@num = num
|
11
|
+
@og = outletgroup
|
12
|
+
end
|
13
|
+
|
14
|
+
def sensor_type
|
15
|
+
:onoff
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(uncached = false)
|
19
|
+
{'ON' => 1.0, 'OFF' => 0.0}[@og.state(@num)]
|
20
|
+
end
|
21
|
+
|
22
|
+
def write(value)
|
23
|
+
newstate = {1.0 => 'ON', 0.0 => 'OFF'}[value]
|
24
|
+
if newstate
|
25
|
+
@og.set_state(@num,newstate)
|
26
|
+
value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class OutletGroup
|
32
|
+
DEFAULT_TIMEOUT = 2
|
33
|
+
DEFAULT_CACHE_TIMEOUT = 60
|
34
|
+
DEFAULT_OUTLETS = {}
|
35
|
+
DEFAULT_DESCRIPTIONS = {}
|
36
|
+
|
37
|
+
attr_accessor :host, :port, :pass, :timeout, :cache_timeout
|
38
|
+
|
39
|
+
def initialize(opts={})
|
40
|
+
@host = opts[:host] || opts['host'] || 'localhost'
|
41
|
+
@port = opts[:port] || opts['port'] || 80
|
42
|
+
@pass = opts[:pass] || opts['pass'] || 'admin'
|
43
|
+
@timeout = opts[:timeout] || opts['timeout'] || DEFAULT_TIMEOUT
|
44
|
+
@cache_timeout = opts[:cache_timeout] || opts['cache_timeout'] || DEFAULT_CACHE_TIMEOUT
|
45
|
+
@outlets = opts[:outlets] || opts["outlets"] || DEFAULT_OUTLETS
|
46
|
+
@descriptions = opts[:descriptions] || opts["descriptions"] || DEFAULT_DESCRIPTIONS
|
47
|
+
@cache = nil
|
48
|
+
@cachehit = nil
|
49
|
+
@cachetime = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def state(num)
|
53
|
+
if !@cachetime or @cachetime < Time.now - @cache_timeout
|
54
|
+
@cache = do_get("/current_state.json?pw=#{@pass}")
|
55
|
+
@cachetime = Time.now
|
56
|
+
end
|
57
|
+
return nil if !@cache
|
58
|
+
json = JSON.parse(@cache.body)
|
59
|
+
num = num - 1
|
60
|
+
if json &&
|
61
|
+
json["CurrentState"] &&
|
62
|
+
json["CurrentState"]["Output"] &&
|
63
|
+
json["CurrentState"]["Output"][num] &&
|
64
|
+
json["CurrentState"]["Output"][num]["Value"]
|
65
|
+
val = json["CurrentState"]["Output"][num]["Value"]
|
66
|
+
{"1" => "ON", "0" => "OFF"}[val]
|
67
|
+
else
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_state(num, state)
|
73
|
+
@cachetime = nil
|
74
|
+
val = {"ON" => "1", "OFF" => "0"}[state]
|
75
|
+
if val
|
76
|
+
response = do_get("/current_state.json?pw=#{@pass}&Relay#{num}=#{val}")
|
77
|
+
response != nil
|
78
|
+
else
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_sensors
|
84
|
+
sensors = {}
|
85
|
+
(1..16).each do |num|
|
86
|
+
name = @outlets[num]
|
87
|
+
if name
|
88
|
+
description = @descriptions[num] || ""
|
89
|
+
sensors[name] = [Outlet.new(num, self), description]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
sensors
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def do_get(path)
|
97
|
+
SAAL::do_http_get(@host, @port, path, nil, nil, @timeout)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|