saal 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39379f2c03a4d1dfe41a1d481d2d90484b9d4e11b2c498f03c0f9ec064918850
4
- data.tar.gz: ab741ee35c0c4528b89dbdbf32e17ca591d134f126db27e7a6974ce51e4a9723
3
+ metadata.gz: 62d7f9b51083356448d50bcb5ad66c729d84a38e152f85470ca4fa5f9f5fed15
4
+ data.tar.gz: 73f51e4736d14ee249531535577a8ff0568e0c19fb3115a4cf76bb0801d8e504
5
5
  SHA512:
6
- metadata.gz: b2532eff01ab6a191106abe8984b46aa0f9b9b0ca461b87b6b09b5174334c4d054ea8ac335e17551e5c733ca0a1dd31356bc25d68b0d38140b27f89b041f257b
7
- data.tar.gz: 4bc1474527de78d0a7c80248376f0ae56a4fbc680cbfa991851f41931b5f2bd78c5073ae6ea4217d4edb3b55fec8e105963026b3ca229b90d0868e7f0cfc7179
6
+ metadata.gz: 349eed054b1fd37de656840840b92e1be279e77fe677b92cc47864fe96079f88e6dc784e608bf5a7843b08cc0025b1f141d1bf88be1cef553e18f104cc0c3d74
7
+ data.tar.gz: e07a22761c8f4f6c01d631abb29b01c63d6888c449fd67762e558ddd7693cd1bdfdf5b8bd7cdc35575006c6cb2fc9cbef49329bf08b754d5101d5dfc20d3421d
@@ -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,143 @@
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
+ )
134
+ envoy.set_all_inverters!
135
+ inverters = envoy.create_sensors
136
+ puts "Found #{envoy.inverters.size} inverters"
137
+ envoy.inverters.each do |serial|
138
+ puts "INVERTER: #{serial} \
139
+ date:#{l(inverters,"inverter_#{serial}_last_report_date")} \
140
+ lastWatts:#{l(inverters,"inverter_#{serial}_w_now")} \
141
+ maxWatts:#{l(inverters,"inverter_#{serial}_w_max")} \
142
+ "
143
+ end
@@ -61,25 +61,8 @@ module SAAL
61
61
 
62
62
  private
63
63
  def do_get(path)
64
- begin
65
- http = Net::HTTP.new(@host,@port)
66
- # Timeout faster when the other side doesn't respond
67
- http.open_timeout = @timeout
68
- http.read_timeout = @timeout
69
- req = Net::HTTP::Get.new(path)
70
- req.basic_auth @user, @pass
71
- response = http.request(req)
72
- if response.code != "200"
73
- #$stderr.puts "ERROR: Code #{response.code}"
74
- #$stderr.puts response.body
75
- return nil
76
- end
77
- return response
78
- rescue Exception
79
- return nil
80
- end
64
+ SAAL::do_http_get(@host, @port, path, @user, @pass, @timeout)
81
65
  end
82
-
83
66
  def parse_index_html(str)
84
67
  doc = Nokogiri::HTML(str)
85
68
  outlets = doc.css('tr[bgcolor="#F4F4F4"]')
@@ -0,0 +1,284 @@
1
+ require 'json'
2
+
3
+ module SAAL
4
+ module Envoy
5
+ class PowerEnergyUnderlying < SensorUnderlying
6
+ def initialize(key, production)
7
+ @key = key
8
+ @production = production
9
+ end
10
+
11
+ def read(uncached = false)
12
+ @production.read_val(@key)
13
+ end
14
+ end
15
+
16
+ class PowerEnergy
17
+ DEFAULT_HOST = "envoy.local"
18
+ DEFAULT_TIMEOUT = 2
19
+ DEFAULT_CACHE_TIMEOUT = 50
20
+ DEFAULT_SOURCES = [
21
+ "production_inverters",
22
+ "production_phase1", "production_phase2", "production_phase3", "production_total",
23
+ "net_consumption_phase1", "net_consumption_phase2", "net_consumption_phase3", "net_consumption_total",
24
+ "total_consumption_phase1", "total_consumption_phase2", "total_consumption_phase3", "total_consumption_total",
25
+ ]
26
+ DEFAULT_TYPES = [
27
+ "w_now", "wh_lifetime", "va_now", "vah_lifetime",
28
+ ]
29
+ DEFAULT_PREFIX = "pv"
30
+
31
+ def initialize(defs, opts={})
32
+ @host = defs[:host] || defs['host'] || DEFAULT_HOST
33
+ @timeout = opts[:timeout] || opts['timeout'] || DEFAULT_TIMEOUT
34
+ @cache_timeout = opts[:cache_timeout] || opts['cache_timeout'] || DEFAULT_CACHE_TIMEOUT
35
+ @cache = nil
36
+ @cachetime = nil
37
+ @sources = defs[:sources] || defs['source'] || DEFAULT_SOURCES
38
+ @types = defs[:types] || defs['types'] || DEFAULT_TYPES
39
+ @prefix = defs[:prefix] || defs['prefix'] || DEFAULT_PREFIX
40
+ end
41
+
42
+ def read_val(name)
43
+ if !@cachetime or @cachetime < Time.now - @cache_timeout
44
+ @cache = read_all()
45
+ @cachetime = Time.now
46
+ end
47
+ return @cache ? @cache[name] : nil
48
+ end
49
+
50
+ def create_sensors
51
+ sensors = {}
52
+ @sources.product(@types).each do |source, type|
53
+ key = "#{@prefix}_#{source}_#{type}"
54
+ sensors[key] = PowerEnergyUnderlying.new(key, self)
55
+ end
56
+ sensors
57
+ end
58
+
59
+ private
60
+ def save_vals(dest, name, source)
61
+ {
62
+ "wNow" => "w_now",
63
+ "apprntPwr" => "va_now",
64
+ "whLifetime" => "wh_lifetime",
65
+ "vahLifetime" => "vah_lifetime",
66
+ }.each do |type, label|
67
+ dest["#{@prefix}_#{name}_#{label}"] = source[type]
68
+ end
69
+
70
+ # Hack around the fact that apprntPwr is broken on the total consumption
71
+ # calculation for the three-phase sum at least
72
+ # In those cases it seems to be missing a divide by three, so when the
73
+ # calculation for voltage and current alone is close do the extra divide
74
+ va_now = dest["#{@prefix}_#{name}_va_now"]
75
+ if va_now && !name.include?("phase")
76
+ voltage = source["rmsVoltage"]
77
+ current = source["rmsCurrent"]
78
+ if voltage && current
79
+ va_alt = voltage * current
80
+ if ((va_alt / va_now) - 1.0).abs < 0.05
81
+ dest["#{@prefix}_#{name}_va_now"] = va_now / 3.0
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def read_all
88
+ response = SAAL::do_http_get(@host, 80, "/production.json?details=1", nil, nil, @timeout)
89
+ return nil if !response
90
+
91
+ values = JSON.parse(response.body)
92
+ outputs = {}
93
+
94
+ values["production"].each do |source|
95
+ type = source["type"]
96
+ case type
97
+ when "inverters"
98
+ save_vals(outputs, "production_inverters", source)
99
+ when "eim"
100
+ if source["lines"]
101
+ save_vals(outputs, "production_phase1", source["lines"][0])
102
+ save_vals(outputs, "production_phase2", source["lines"][1])
103
+ save_vals(outputs, "production_phase3", source["lines"][2])
104
+ end
105
+ save_vals(outputs, "production_total", source)
106
+ else
107
+ $stderr.puts "WARNING: ENVOY: don't know source type #{type}"
108
+ end
109
+ end
110
+
111
+ values["consumption"].each do |source|
112
+ type = {
113
+ "total-consumption" => "total",
114
+ "net-consumption" => "net",
115
+ }[source["measurementType"]] || "unknown";
116
+
117
+ if source["lines"]
118
+ save_vals(outputs, "#{type}_consumption_phase1", source["lines"][0])
119
+ save_vals(outputs, "#{type}_consumption_phase2", source["lines"][1])
120
+ save_vals(outputs, "#{type}_consumption_phase3", source["lines"][2])
121
+ end
122
+ save_vals(outputs, "#{type}_consumption_total", source)
123
+ end
124
+
125
+ outputs
126
+ end
127
+ end
128
+
129
+ class ACQualityUnderlying < SensorUnderlying
130
+ def initialize(key, production)
131
+ @key = key
132
+ @production = production
133
+ end
134
+
135
+ def read(uncached = false)
136
+ @production.read_val(@key)
137
+ end
138
+ end
139
+
140
+ class ACQuality
141
+ DEFAULT_HOST = "envoy.local"
142
+ DEFAULT_TIMEOUT = 2
143
+ DEFAULT_CACHE_TIMEOUT = 50
144
+ DEFAULT_SOURCES = ["total","phase1","phase2","phase3",]
145
+ DEFAULT_TYPES = ["frequency","voltage"]
146
+ DEFAULT_PREFIX = "ac"
147
+
148
+ def initialize(defs, opts={})
149
+ @host = defs[:host] || defs['host'] || DEFAULT_HOST
150
+ @timeout = opts[:timeout] || opts['timeout'] || DEFAULT_TIMEOUT
151
+ @cache_timeout = opts[:cache_timeout] || opts['cache_timeout'] || DEFAULT_CACHE_TIMEOUT
152
+ @cache = nil
153
+ @cachetime = nil
154
+ @sources = defs[:sources] || defs['source'] || DEFAULT_SOURCES
155
+ @types = defs[:types] || defs['types'] || DEFAULT_TYPES
156
+ @prefix = defs[:prefix] || defs['prefix'] || DEFAULT_PREFIX
157
+ end
158
+
159
+ def read_val(name)
160
+ if !@cachetime or @cachetime < Time.now - @cache_timeout
161
+ @cache = read_all()
162
+ @cachetime = Time.now
163
+ end
164
+ return @cache ? @cache[name] : nil
165
+ end
166
+
167
+ def create_sensors
168
+ sensors = {}
169
+ @sources.product(@types).each do |source, type|
170
+ key = "#{@prefix}_#{source}_#{type}"
171
+ sensors[key] = ACQualityUnderlying.new(key, self)
172
+ end
173
+ sensors
174
+ end
175
+
176
+ private
177
+ def save_vals(dest, name, source)
178
+ {
179
+ "voltage" => "voltage",
180
+ "freq" => "frequency",
181
+ }.each do |type, label|
182
+ dest["#{@prefix}_#{name}_#{label}"] = source[type]
183
+ end
184
+ end
185
+
186
+ def read_all
187
+ response = SAAL::do_http_get(@host, 80, "/ivp/meters/readings", nil, nil, @timeout)
188
+ return nil if !response
189
+
190
+ values = JSON.parse(response.body)
191
+ outputs = {}
192
+ source = values[0]
193
+ save_vals(outputs, "total", source)
194
+ if source["channels"]
195
+ save_vals(outputs, "phase1", source["channels"][0])
196
+ save_vals(outputs, "phase2", source["channels"][1])
197
+ save_vals(outputs, "phase3", source["channels"][2])
198
+ end
199
+
200
+ outputs
201
+ end
202
+ end
203
+
204
+ class InverterUnderlying < SensorUnderlying
205
+ def initialize(key, inverters)
206
+ @key = key
207
+ @inverters = inverters
208
+ end
209
+
210
+ def read(uncached = false)
211
+ @inverters.read_val(@key)
212
+ end
213
+ end
214
+
215
+ class Inverters
216
+ DEFAULT_TIMEOUT = 2
217
+ DEFAULT_CACHE_TIMEOUT = 50
218
+ DEFAULT_SOURCES = []
219
+ DEFAULT_TYPES = ["last_report_date", "watts_now", "watts_max"]
220
+ DEFAULT_USER = nil
221
+ DEFAULT_PASSWORD = nil
222
+ attr_reader :inverters
223
+
224
+ def initialize(defs, opts={})
225
+ @host = defs[:host] || defs['host'] || DEFAULT_HOST
226
+ @user = defs[:user] || defs['user'] || DEFAULT_USER
227
+ @password = defs[:password] || defs['password'] || DEFAULT_PASSWORD
228
+ @timeout = opts[:timeout] || opts['timeout'] || DEFAULT_TIMEOUT
229
+ @cache_timeout = opts[:cache_timeout] || opts['cache_timeout'] || DEFAULT_CACHE_TIMEOUT
230
+ @cache = nil
231
+ @cachetime = nil
232
+ @inverters_list = {}
233
+ @inverters = defs[:inverters] || defs['inverters'] || DEFAULT_SOURCES
234
+ @types = defs[:types] || defs['types'] || DEFAULT_TYPES
235
+ end
236
+
237
+ def read_val(name)
238
+ if !@cachetime or @cachetime < Time.now - @cache_timeout
239
+ @cache = read_all()
240
+ @cachetime = Time.now
241
+ end
242
+ return @cache ? @cache[name] : nil
243
+ end
244
+
245
+ def enumerate
246
+ read_val("foo") # Force a read to make sure the inverter serials are stored
247
+ @inverters_list.keys
248
+ end
249
+
250
+ def set_all_inverters!
251
+ @inverters = self.enumerate
252
+ end
253
+
254
+ def create_sensors
255
+ sensors = {}
256
+ @inverters.product(@types).each do |source, type|
257
+ key = "inverter_#{source}_#{type}"
258
+ sensors[key] = InverterUnderlying.new(key, self)
259
+ end
260
+ sensors
261
+ end
262
+
263
+ private
264
+ def read_all
265
+ response = SAAL::do_http_get_digest(@host, 80, "/api/v1/production/inverters", @user, @password, @timeout)
266
+ return nil if !response
267
+
268
+ values = JSON.parse(response.body)
269
+ inverters = {}
270
+ values.each do |inverter|
271
+ {"lastReportDate" => "last_report_date",
272
+ "lastReportWatts" => "watts_now",
273
+ "maxReportWatts" => "watts_max",
274
+ }.each do |type, label|
275
+ inverters["inverter_#{inverter["serialNumber"]}_#{label}"] = inverter[type]
276
+ @inverters_list[inverter["serialNumber"]] = true
277
+ end
278
+ end
279
+
280
+ inverters
281
+ end
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,51 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'net/http/digest_auth'
4
+
5
+ def SAAL::do_http_get(host, port, path, user, pass, timeout)
6
+ begin
7
+ http = Net::HTTP.new(host,port)
8
+ # Timeout faster when the other side doesn't respond
9
+ http.open_timeout = timeout
10
+ http.read_timeout = timeout
11
+ req = Net::HTTP::Get.new(path)
12
+ req.basic_auth(user, pass) if user && pass
13
+ response = http.request(req)
14
+ if response.code != "200"
15
+ #$stderr.puts "ERROR: Code #{response.code}"
16
+ #$stderr.puts response.body
17
+ return nil
18
+ end
19
+ return response
20
+ rescue Exception
21
+ return nil
22
+ end
23
+ end
24
+
25
+ def SAAL::do_http_get_digest(host, port, path, user, pass, timeout)
26
+ begin
27
+ uri = URI.parse "http://#{host}:#{port}/#{path}"
28
+ digest_auth = Net::HTTP::DigestAuth.new
29
+ uri.user = user
30
+ uri.password = pass
31
+ http = Net::HTTP.new(host,port)
32
+ # Timeout faster when the other side doesn't respond
33
+ http.open_timeout = timeout
34
+ http.read_timeout = timeout
35
+ req = Net::HTTP::Get.new(path)
36
+ response = http.request(req)
37
+ if response.code == "401" && user && pass
38
+ auth = digest_auth.auth_header uri, response['www-authenticate'], 'GET'
39
+ req.add_field 'Authorization', auth
40
+ response = http.request(req)
41
+ end
42
+ if response.code != "200"
43
+ #$stderr.puts "ERROR: Code #{response.code}"
44
+ #$stderr.puts response.body
45
+ return nil
46
+ end
47
+ return response
48
+ rescue Exception
49
+ return nil
50
+ end
51
+ end
@@ -10,7 +10,7 @@ module SAAL
10
10
  DBCONF = CONFDIR+"database.yml"
11
11
  CHARTSCONF = CONFDIR+"charts.yml"
12
12
 
13
- VERSION = '0.3.0'
13
+ VERSION = '0.3.1'
14
14
  end
15
15
 
16
16
  require File.dirname(__FILE__)+'/dbstore.rb'
@@ -23,4 +23,6 @@ require File.dirname(__FILE__)+'/chart.rb'
23
23
  require File.dirname(__FILE__)+'/chart_data.rb'
24
24
  require File.dirname(__FILE__)+'/outliercache.rb'
25
25
  require File.dirname(__FILE__)+'/dinrelay.rb'
26
+ require File.dirname(__FILE__)+'/envoy.rb'
27
+ require File.dirname(__FILE__)+'/http.rb'
26
28
 
@@ -40,9 +40,23 @@ module SAAL
40
40
  defs.merge!('name' => outlet_descriptions[num])
41
41
  Sensor.new(dbstore, oname, DINRelay::Outlet.new(num.to_i, og), defs, opts)
42
42
  end
43
+ elsif defs['envoy_power_energy']
44
+ defs = defs['envoy_power_energy'].merge('prefix' => name)
45
+ pe = SAAL::Envoy::PowerEnergy::new(defs)
46
+ sensors = pe.create_sensors
47
+ return sensors.map do |name, underlying|
48
+ Sensor.new(dbstore, name, underlying, defs, opts)
49
+ end
50
+ elsif defs['envoy_ac_quality']
51
+ defs = defs['envoy_ac_quality'].merge('prefix' => name)
52
+ pe = SAAL::Envoy::ACQuality::new(defs)
53
+ sensors = pe.create_sensors
54
+ return sensors.map do |name, underlying|
55
+ Sensor.new(dbstore, name, underlying, defs, opts)
56
+ end
43
57
  else
44
- raise UnknownSensorType, "Couldn't figure out a valid sensor type "
45
- "from the configuration for #{name}"
58
+ p defs, name
59
+ raise UnknownSensorType, "Couldn't figure out a valid sensor type for #{name}"
46
60
  end
47
61
  end
48
62
  end
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
6
6
  s.platform = Gem::Platform::RUBY
7
7
 
8
8
  s.name = 'saal'
9
- s.version = '0.3.0'
10
- s.date = '2018-06-02'
9
+ s.version = '0.3.1'
10
+ s.date = '2020-12-19'
11
11
 
12
12
  s.summary = "Thin abstraction layer for interfacing and recording sensors (currently onewire) and actuators (currently dinrelay)"
13
13
  s.description = <<EOF
@@ -32,6 +32,7 @@ EOF
32
32
  s.add_runtime_dependency 'ownet', "~>0.2"
33
33
  s.add_runtime_dependency 'mysql2', "~>0.5"
34
34
  s.add_runtime_dependency 'nokogiri', '~>1.8'
35
+ s.add_runtime_dependency 'net-http-digest_auth', '~>1.4'
35
36
 
36
37
  # = MANIFEST =
37
38
  s.files = %w[
@@ -45,6 +46,7 @@ EOF
45
46
  bin/saal_chart
46
47
  bin/saal_daemon
47
48
  bin/saal_dump_database
49
+ bin/saal_envoy_read
48
50
  bin/saal_import_mysql
49
51
  bin/saal_readall
50
52
  lib/chart.rb
@@ -53,6 +55,8 @@ EOF
53
55
  lib/daemon.rb
54
56
  lib/dbstore.rb
55
57
  lib/dinrelay.rb
58
+ lib/envoy.rb
59
+ lib/http.rb
56
60
  lib/outliercache.rb
57
61
  lib/owsensor.rb
58
62
  lib/saal.rb
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pedro Côrte-Real
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-02 00:00:00.000000000 Z
11
+ date: 2020-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ownet
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: net-http-digest_auth
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
55
69
  description: "A daemon and libraries to create an abstraction layer that interfaces
56
70
  with \nsensors and actuators, recording their state, responding to requests \nfor
57
71
  current and historical values, and allowing changes of state.\n"
@@ -60,12 +74,12 @@ executables:
60
74
  - dinrelaystatus
61
75
  - dinrelayset
62
76
  - saal_import_mysql
63
- - saal_chart~
64
- - saal_daemon~
65
77
  - saal_dump_database
66
78
  - saal_readall
67
79
  - saal_chart
68
80
  - saal_daemon
81
+ - saal_envoy_generate_config
82
+ - saal_envoy_read
69
83
  extensions: []
70
84
  extra_rdoc_files:
71
85
  - README.rdoc
@@ -79,10 +93,10 @@ files:
79
93
  - bin/dinrelayset
80
94
  - bin/dinrelaystatus
81
95
  - bin/saal_chart
82
- - bin/saal_chart~
83
96
  - bin/saal_daemon
84
- - bin/saal_daemon~
85
97
  - bin/saal_dump_database
98
+ - bin/saal_envoy_generate_config
99
+ - bin/saal_envoy_read
86
100
  - bin/saal_import_mysql
87
101
  - bin/saal_readall
88
102
  - lib/chart.rb
@@ -91,6 +105,8 @@ files:
91
105
  - lib/daemon.rb
92
106
  - lib/dbstore.rb
93
107
  - lib/dinrelay.rb
108
+ - lib/envoy.rb
109
+ - lib/http.rb
94
110
  - lib/outliercache.rb
95
111
  - lib/owsensor.rb
96
112
  - lib/saal.rb
@@ -118,7 +134,7 @@ homepage: https://github.com/pedrocr/saal
118
134
  licenses:
119
135
  - LGPL-2.1
120
136
  metadata: {}
121
- post_install_message:
137
+ post_install_message:
122
138
  rdoc_options:
123
139
  - "-S"
124
140
  - "-w 2"
@@ -137,9 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
153
  - !ruby/object:Gem::Version
138
154
  version: '0'
139
155
  requirements: []
140
- rubyforge_project:
141
- rubygems_version: 2.7.6
142
- signing_key:
156
+ rubygems_version: 3.1.2
157
+ signing_key:
143
158
  specification_version: 2
144
159
  summary: Thin abstraction layer for interfacing and recording sensors (currently onewire)
145
160
  and actuators (currently dinrelay)
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- NUM_VALUES_SMALL = 500 # Datapoints in "small" charts
4
- NUM_VALUES_LARGE = 150 # Datapoints in "large" charts
5
- LARGE_CHART_THRESHOLD = 30*24*60*60 # Threshold for a large chart (in seconds)
6
-
7
- require File.dirname(__FILE__)+'/../lib/saal.rb'
8
-
9
- def usage
10
- $stderr.puts("Usage: saal_chart <chart dir>")
11
- end
12
-
13
- if ARGV.size != 1
14
- usage
15
- exit (2)
16
- end
17
-
18
- SENSOR_RANGES = {:temperature=>[-15, 45], :humidity=>[0,100], :pressure=>[950,1050]}
19
-
20
-
21
- SAAL::Charts.new.each do |chart|
22
- $stderr.puts "Generating chart #{chart.name}"
23
-
24
- pngfile = ARGV[0]+'/chart-'+chart.name.to_s+'.png'
25
- ymlfile = ARGV[0]+'/chart-'+chart.name.to_s+'.yml'
26
-
27
- @mins = chart.minimum
28
- @maxs = chart.maximum
29
- @avgs = chart.average
30
- @minmax = {}
31
- chart.sensors.each do |s|
32
- s = s.name.to_sym
33
- @minmax[s] = {:maximum => @maxs[s], :minimum => @mins[s], :average => @avgs[s]}
34
- end
35
-
36
- File.open(ymlfile, 'w').write(YAML::dump(@minmax))
37
-
38
- def normalize_data(data, min, max)
39
- data.map do |i|
40
- if i.nil?
41
- -1.0
42
- elsif i < min
43
- 0.0
44
- elsif i > max
45
- 100.0
46
- else
47
- (((i-min)/(max-min).to_f)*1000).round/10.0
48
- end
49
- end
50
- end
51
-
52
- @periodnames = chart.periodnames
53
- @numperiods = @periodnames.size
54
- num_values = ((chart.to-chart.from)>LARGE_CHART_THRESHOLD) ?
55
- NUM_VALUES_LARGE :
56
- NUM_VALUES_SMALL
57
-
58
- @averages = chart.average(num_values)
59
-
60
- @data = chart.sensors.map do |sensor|
61
- normalize_data(@averages[sensor.name.to_sym], *(SENSOR_RANGES[sensor.sensor_type]))
62
- end
63
-
64
- @dataurl = @data.map {|values| values.join(",")}.join('|')
65
-
66
- r = {}
67
- case chart.alignlabels
68
- when :center
69
- @periodnamesurl = "||"+@periodnames.join('||')+"||"
70
- when :left
71
- @periodnamesurl = "|"+@periodnames.join('|')+"||"
72
- r[:chxs] = "0,555555,11,-1,lt"
73
- end
74
- @xincr = 100.0/@numperiods.to_f*10000.truncate.to_f/10000
75
-
76
- r[:chof] = "png"
77
- r[:chs] = "700x300"
78
- r[:cht] = "lc"
79
- r[:chco] = "00ff00,ff0000,0000ff,ff9933,800080"
80
- r[:chxt] = "x,y,y,r"
81
- 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"
82
- r[:chg] = "#{@xincr},12.5,1,5"
83
- r[:chd] = "t:#{@dataurl}"
84
-
85
- @url = "http://chart.apis.google.com/chart?&"
86
- @postdata = r.map{|k,v| k.to_s+"="+v}.join("&")
87
-
88
-
89
- system "wget --quiet \"#{@url}\" --post-data=\"#{@postdata}\" -O #{pngfile}"
90
- end
@@ -1,31 +0,0 @@
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
- def usage
10
- $stderr.puts "Usage: saal_daemon <pidfile|--foreground>"
11
- end
12
-
13
- if ARGV.size != 1
14
- usage
15
- exit 2
16
- else
17
- pidfile = ARGV[0]
18
- foreground = (ARGV[0] == '--foreground')
19
- d = SAAL::Daemon.new(:interval => SENSOR_INTERVAL,
20
- :sensorconf => SENSORSCONF,
21
- :dbconf => DBCONF,
22
- :foreground => foreground,
23
- :keep_stdin => true)
24
- pid = d.run
25
- if !foreground
26
- File.open(pidfile, 'w') do |f|
27
- f.write(pid)
28
- f.close
29
- end
30
- end
31
- end