saal 0.2.25 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a86991bf6a2c0ddf03cab4c239953c2c4e819f5019145398f4b7a527e928852
4
+ data.tar.gz: de08c727c732e4afb86cafcdcadfeec1f16fb127d675714b03bf388956bc6646
5
+ SHA512:
6
+ metadata.gz: db0fe9ccfd6e51da7bec292c9cd44c0b8c994ca238772cd817941f22f823f8ac8098ef005373e2f86dfcd9d121dc6470bdd3615ba961d1c514c620e8cf399095
7
+ data.tar.gz: fa64aa3d8cea43fc12e07db8d84def9ba45ca27655dbb95251e7b7c45e8c322b5c8ee0f58b02b4c63c9799f15a5db1316e0e174a6b246ee4725887d3ce12d823
@@ -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
@@ -0,0 +1,152 @@
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 fdispd(val)
35
+ if val
36
+ Time.at(val.to_i).strftime("%Y-%m-%d %k:%M:%S")
37
+ else
38
+ "n/a"
39
+ end
40
+ end
41
+
42
+ def l(vals, name)
43
+ sensor = vals[name]
44
+ if sensor
45
+ sensor.read
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ def lratio(vals, name1, name2)
52
+ v1 = l(vals, name1)
53
+ v2 = l(vals, name2)
54
+ if v1 && v2
55
+ v1 / v2
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
61
+ ac_quality = SAAL::Envoy::ACQuality::new(:host => ARGV[0]).create_sensors
62
+
63
+ puts " ========= AC QUALITY ========"
64
+ puts " voltage (V) freq (Hz)"
65
+ def qual_line(vals,name, type)
66
+ puts " #{name} \
67
+ #{fdisp_dec(l(vals,"ac_#{type}_voltage"))} \
68
+ #{fdisp_dec(l(vals,"ac_#{type}_frequency"))} \
69
+ "
70
+ end
71
+ qual_line(ac_quality, "Total: ", "total")
72
+ qual_line(ac_quality, "Phase1:", "phase1")
73
+ qual_line(ac_quality, "Phase2:", "phase2")
74
+ qual_line(ac_quality, "Phase3:", "phase3")
75
+
76
+ production = SAAL::Envoy::PowerEnergy::new(:host => ARGV[0]).create_sensors
77
+
78
+ puts ""
79
+ puts " ============ TRUE POWER (W) ============ ======= TRUE ENERGY (kWh) ======="
80
+ puts " consumption production net consumption production net"
81
+ def p_line(vals, name, type, metric)
82
+ puts " #{name} \
83
+ #{fdisp(l(vals,"pv_total_consumption_#{type}_#{metric}_now"))} \
84
+ #{fdisp(l(vals,"pv_production_#{type}_#{metric}_now"))} \
85
+ #{fdisp(l(vals,"pv_net_consumption_#{type}_#{metric}_now"))} \
86
+ #{fdispk(l(vals,"pv_total_consumption_#{type}_#{metric}h_lifetime"))} \
87
+ #{fdispk(l(vals,"pv_production_#{type}_#{metric}h_lifetime"))} \
88
+ #{fdispk(l(vals,"pv_net_consumption_#{type}_#{metric}h_lifetime"))} \
89
+ "
90
+ end
91
+ p_line(production, "Total: ", "total", "w")
92
+ p_line(production, "Phase1:", "phase1", "w")
93
+ p_line(production, "Phase2:", "phase2", "w")
94
+ p_line(production, "Phase3:", "phase3", "w")
95
+ puts " Total Inverters: \
96
+ #{fdisp(l(production,"pv_production_inverters_w_now"))} \
97
+ \
98
+ #{fdispk(l(production,"pv_production_inverters_wh_lifetime"))} \
99
+ "
100
+
101
+ puts ""
102
+ puts " ========== APPARENT POWER (VA) ========= ===== APPARENT ENERGY (kVAh) ===="
103
+ puts " consumption production net consumption production net"
104
+ p_line(production, "Total: ", "total", "va")
105
+ p_line(production, "Phase1:", "phase1", "va")
106
+ p_line(production, "Phase2:", "phase2", "va")
107
+ p_line(production, "Phase3:", "phase3", "va")
108
+
109
+ def pf_line(vals, name, type, metric)
110
+ pf_total_consumption_instant = lratio(vals,"pv_total_consumption_#{type}_w_now","pv_total_consumption_#{type}_va_now")
111
+ pf_total_production_instant = lratio(vals,"pv_production_#{type}_w_now","pv_production_#{type}_va_now")
112
+ pf_net_production_instant = lratio(vals,"pv_net_consumption_#{type}_w_now","pv_net_consumption_#{type}_va_now")
113
+
114
+ pf_total_consumption_lifetime = lratio(vals,"pv_total_consumption_#{type}_wh_lifetime","pv_total_consumption_#{type}_vah_lifetime")
115
+ pf_total_production_lifetime = lratio(vals,"pv_production_#{type}_wh_lifetime","pv_production_#{type}_vah_lifetime")
116
+ pf_net_production_lifetime = lratio(vals,"pv_net_consumption_#{type}_wh_lifetime","pv_net_consumption_#{type}_vah_lifetime")
117
+
118
+ puts " #{name} \
119
+ #{fdisp_dec(pf_total_consumption_instant)} \
120
+ #{fdisp_dec(pf_total_production_instant)} \
121
+ #{fdisp_dec(pf_net_production_instant)} \
122
+ #{fdisp_dec(pf_total_consumption_lifetime)} \
123
+ #{fdisp_dec(pf_total_production_lifetime)} \
124
+ #{fdisp_dec(pf_net_production_lifetime)} \
125
+ "
126
+ end
127
+
128
+ puts ""
129
+ puts " ========= INSTANT POWER FACTOR ========= ====== LIFETIME POWER FACTOR ====="
130
+ puts " consumption production net consumption production net"
131
+ pf_line(production, "Total: ", "total", "va")
132
+ pf_line(production, "Phase1:", "phase1", "va")
133
+ pf_line(production, "Phase2:", "phase2", "va")
134
+ pf_line(production, "Phase3:", "phase3", "va")
135
+
136
+ puts ""
137
+ envoy = SAAL::Envoy::Inverters::new(
138
+ :host => ARGV[0],
139
+ :user => ARGV[1],
140
+ :password => ARGV[2],
141
+ :types => ["w_now", "last_report_date", "w_max"],
142
+ )
143
+ envoy.set_all_inverters!
144
+ inverters = envoy.create_sensors
145
+ puts " Found #{envoy.inverters.size} inverters"
146
+ envoy.inverters.each do |serial|
147
+ puts " INVERTER: #{serial} \
148
+ date:#{fdispd(l(inverters,"inverters_#{serial}_last_report_date"))} \
149
+ lastWatts:#{l(inverters,"inverters_#{serial}_w_now")} \
150
+ maxWatts:#{l(inverters,"inverters_#{serial}_w_max")} \
151
+ "
152
+ 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{|name| sensors.send(name)}
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
@@ -39,7 +39,7 @@ module SAAL
39
39
  (0..num-2).map do |i|
40
40
  f = t + 1
41
41
  t = (f+step)
42
- v = sensor.send(method, f.to_i, t.to_i)
42
+ _v = sensor.send(method, f.to_i, t.to_i)
43
43
  end << sensor.send(method, (t+1).to_i, to.to_i)
44
44
  end
45
45
 
data/lib/dbstore.rb CHANGED
@@ -35,6 +35,32 @@ module SAAL
35
35
  db_range("AVG", sensor, from, to)
36
36
  end
37
37
 
38
+ def weighted_average(sensor, from, to)
39
+ total_time = 0
40
+ total_value = 0.0
41
+ previous_value = nil
42
+ start_time = nil
43
+ initialized = false
44
+ db_query "SELECT date,value FROM sensor_reads
45
+ WHERE sensor = '#{db_quote(sensor.to_s)}'
46
+ AND date >= #{from.to_s}
47
+ AND date <= #{to.to_s}" do |r|
48
+ r.each do |row|
49
+ date = row["date"].to_i
50
+ value = row["value"].to_f
51
+ if start_time
52
+ elapsed = date - start_time
53
+ total_value += elapsed * previous_value
54
+ total_time += elapsed
55
+ initialized = true
56
+ end
57
+ start_time = date
58
+ previous_value = value
59
+ end
60
+ end
61
+ initialized ? total_value / total_time : nil
62
+ end
63
+
38
64
  def minimum(sensor, from, to)
39
65
  db_range("MIN", sensor, from, to)
40
66
  end
@@ -46,54 +72,51 @@ module SAAL
46
72
  WHERE sensor = '#{db_quote(sensor.to_s)}'
47
73
  AND date > '#{Time.now.utc.to_i - MAX_LAST_VAL_AGE}'
48
74
  ORDER BY date DESC LIMIT 1" do |r|
49
- if r.num_rows == 0
50
- return nil
75
+ row = r.first
76
+ if row
77
+ _date, value = [row["date"].to_i, row["value"].to_f]
78
+ value
51
79
  else
52
- row = r.fetch_row
53
- date, value = [row[0].to_i, row[1].to_f]
54
- return value
80
+ nil
55
81
  end
56
82
  end
57
83
  end
58
84
 
59
85
  def each
60
86
  db_query "SELECT sensor,date,value FROM sensor_reads" do |r|
61
- r.num_rows.times do
62
- row = r.fetch_row
63
- yield [row[0],row[1].to_i, row[2].to_f]
87
+ r.each do |row|
88
+ yield [row["sensor"],row["date"].to_i, row["value"].to_f]
64
89
  end
65
90
  end
66
91
  end
67
92
 
68
93
  private
69
94
  def db_range(function, sensor, from, to)
70
- db_query "SELECT #{function}(value) AS average FROM sensor_reads
95
+ db_query "SELECT #{function}(value) AS func FROM sensor_reads
71
96
  WHERE sensor = '#{db_quote(sensor.to_s)}'
72
97
  AND date >= #{from.to_s}
73
98
  AND date <= #{to.to_s}" do |r|
74
- if r.num_rows == 0
75
- nil
99
+ row = r.first
100
+ if row && row["func"]
101
+ row["func"].to_f
76
102
  else
77
- row = r.fetch_row
78
- row[0] ? row[0].to_f : nil
103
+ nil
79
104
  end
80
105
  end
81
106
  end
82
107
 
83
108
  def db_quote(text)
84
- Mysql.quote(text)
109
+ Mysql2::Client.escape(text)
85
110
  end
86
111
 
87
112
  def db_query(query, opts={})
88
113
  db = nil
89
114
  begin
90
115
  # connect to the MySQL server
91
- db = Mysql.new(@dbopts['host'],@dbopts['user'],@dbopts['pass'],
92
- @dbopts['db'],@dbopts['port'],@dbopts['socket'],
93
- @dbopts['flags'])
116
+ db = Mysql2::Client.new(@dbopts)
94
117
  res = db.query(query)
95
118
  yield res if block_given?
96
- rescue Mysql::Error => e
119
+ rescue Mysql2::Error => e
97
120
  $stderr.puts "MySQL Error #{e.errno}: #{e.error}" if !(e.errno == opts[:ignoreerr])
98
121
  ensure
99
122
  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 = 5
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