egauge 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/History.txt +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +67 -0
- data/Version.txt +1 -0
- data/lib/egauge.rb +21 -0
- data/lib/egauge/data.rb +79 -0
- data/lib/egauge/register.rb +46 -0
- data/spec/egauge/data_spec.rb +45 -0
- data/spec/egauge/egauge_spec.rb +8 -0
- data/spec/egauge/register_spec.rb +39 -0
- data/spec/samples/data1.xml +11 -0
- data/spec/samples/data2.xml +24 -0
- data/spec/samples/data3.xml +13 -0
- data/spec/spec_helper.rb +14 -0
- metadata +94 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require <%= File.join(File.expand_path(File.dirname(__FILE__)), 'spec', 'spec_helper.rb') %>
|
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Irongaze Consulting LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
= GEM: egauge
|
2
|
+
|
3
|
+
Written by Rob Morris @ Irongaze Consulting LLC (http://irongaze.com)
|
4
|
+
|
5
|
+
Funding for development provided by Sustainable Industrial Solutions LLC (http://sustainableis.com)
|
6
|
+
|
7
|
+
== DESCRIPTION
|
8
|
+
|
9
|
+
A set of classes to enable parsing and using data from eGauge meters as native Ruby objects.
|
10
|
+
|
11
|
+
== SYNOPSIS
|
12
|
+
|
13
|
+
This gem facilitates interpreting incoming monitoring data and converting it into Ruby objects for easier
|
14
|
+
manipulation. It handles parsing the xml data posted by eGauge devices, converting them into Ruby objects
|
15
|
+
that can be manipulated with ease!
|
16
|
+
|
17
|
+
To use:
|
18
|
+
|
19
|
+
# Require the library (not needed if using Rails/bundler)
|
20
|
+
>> require 'egauge'
|
21
|
+
|
22
|
+
# Parse a posted xml chunk to extract the contained data, passing in
|
23
|
+
# the data posted to your server from the eGauge device
|
24
|
+
>> data = EGauge::Data.parse(request.body.read)
|
25
|
+
|
26
|
+
# Which device sent this data?
|
27
|
+
>> data.serial
|
28
|
+
=> '0x37cdd096'
|
29
|
+
|
30
|
+
# How many registers do we have data coming in from?
|
31
|
+
>> data.register_count
|
32
|
+
=> 2
|
33
|
+
|
34
|
+
# How many rows of data?
|
35
|
+
>> data.row_count
|
36
|
+
=> 3
|
37
|
+
|
38
|
+
# Check out a register
|
39
|
+
>> reg = data.registers.first
|
40
|
+
>> reg.label
|
41
|
+
=> 'Solar'
|
42
|
+
>> reg.type_code
|
43
|
+
=> 'P'
|
44
|
+
|
45
|
+
# Get the data points from a given register, with timestamps
|
46
|
+
>> reg.each_with_timestamp do |val, ts|
|
47
|
+
>> puts "#{ts}: #{val}"
|
48
|
+
>> end
|
49
|
+
2012-10-09 13:50:00 -0400: 51140761685
|
50
|
+
2012-10-09 13:51:00 -0400: 51140761792
|
51
|
+
2012-10-09 13:52:00 -0400: 51140761880
|
52
|
+
|
53
|
+
== REQUIREMENTS
|
54
|
+
|
55
|
+
- Ruby 1.9
|
56
|
+
- Nokogiri for XML parsing
|
57
|
+
- Rspec to build and test the gem
|
58
|
+
|
59
|
+
== INSTALL
|
60
|
+
|
61
|
+
To install, simply run:
|
62
|
+
|
63
|
+
sudo gem install egauge
|
64
|
+
|
65
|
+
RVM users can skip the sudo:
|
66
|
+
|
67
|
+
gem install egauge
|
data/Version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/egauge.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Load our dependencies
|
2
|
+
#require 'iron/extensions'
|
3
|
+
|
4
|
+
# Requires all classes
|
5
|
+
search_path = File.join(File.expand_path(File.dirname(__FILE__)), '*', '*.rb')
|
6
|
+
Dir.glob(search_path) do |path|
|
7
|
+
require path
|
8
|
+
end
|
9
|
+
|
10
|
+
module EGauge
|
11
|
+
|
12
|
+
def self.parse_time(str)
|
13
|
+
# Chop off the initial hex flagging if present
|
14
|
+
str = str[2..-1] if str[/^0x/]
|
15
|
+
# Do ruby stuff to convert to 4 byte unsigned int
|
16
|
+
as_int = str.hex
|
17
|
+
# And return as a time...
|
18
|
+
Time.at(as_int)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/egauge/data.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module EGauge
|
4
|
+
class Data
|
5
|
+
# Attributes
|
6
|
+
attr_reader :serial, :registers, :timestamp
|
7
|
+
|
8
|
+
# Parse XML and return an EGauge::Data object containing the data.
|
9
|
+
# This will be the primary entry point for most uses of this gem.
|
10
|
+
def self.parse(xml)
|
11
|
+
doc = Nokogiri::XML(xml)
|
12
|
+
doc.slop!
|
13
|
+
|
14
|
+
data = new(doc)
|
15
|
+
data
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(xml)
|
19
|
+
# Store off Nokogiri xml tree
|
20
|
+
@xml = xml
|
21
|
+
@serial = @xml.group['serial']
|
22
|
+
|
23
|
+
# Get first data segment, which sets the register info for all data segments
|
24
|
+
data = @xml.group.data
|
25
|
+
|
26
|
+
# Save off our starting timestamp for this whole group
|
27
|
+
@timestamp = EGauge::parse_time(data['time_stamp'])
|
28
|
+
|
29
|
+
# Build our registers
|
30
|
+
index = 0
|
31
|
+
@registers = data.cname.collect do |col|
|
32
|
+
reg = EGauge::Register.new(self, index, col)
|
33
|
+
index += 1
|
34
|
+
reg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def xml
|
39
|
+
@xml
|
40
|
+
end
|
41
|
+
|
42
|
+
def num_registers
|
43
|
+
@registers.count
|
44
|
+
end
|
45
|
+
|
46
|
+
def num_rows
|
47
|
+
# Sum the count of rows across each <data> node
|
48
|
+
@xml.group.elements.collect do |chunk|
|
49
|
+
(chunk / './r').count
|
50
|
+
end.inject(&:+)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Run each value in this register
|
54
|
+
def each_with_timestamp
|
55
|
+
@xml.group.elements.each do |chunk|
|
56
|
+
# Set up for running this data chunk - prep timestamp and increment step from source xml
|
57
|
+
ts = EGauge::parse_time(chunk['time_stamp'])
|
58
|
+
step = chunk['time_delta'].to_i
|
59
|
+
|
60
|
+
# Run each row in the chunk, and yield our results
|
61
|
+
(chunk / './r').each do |row|
|
62
|
+
vals = (row / './c').collect {|c| c.text.to_i}
|
63
|
+
yield ts, vals
|
64
|
+
ts += step
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return results as a 2D array, like so: [ [timestamp1, [val1, val2...]], [timestamp2, [val1, val2,...], ... ]
|
70
|
+
def to_a
|
71
|
+
res = []
|
72
|
+
each_with_timestamp do |ts, vals|
|
73
|
+
res << [ts, vals]
|
74
|
+
end
|
75
|
+
res
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
# Represents a single register in the
|
3
|
+
module EGauge
|
4
|
+
class Register
|
5
|
+
|
6
|
+
attr_accessor :index, :label, :type_code
|
7
|
+
|
8
|
+
def initialize(data, index, def_node)
|
9
|
+
@data = data
|
10
|
+
@index = index
|
11
|
+
|
12
|
+
@label = def_node.text
|
13
|
+
@type_code = def_node['t']
|
14
|
+
end
|
15
|
+
|
16
|
+
# Run each value in this register, yielding |timestamp, value| for each
|
17
|
+
def each_with_timestamp
|
18
|
+
@data.xml.group.elements.each do |chunk|
|
19
|
+
# Set up for running this data chunk - prep timestamp and increment step from source xml
|
20
|
+
ts = EGauge::parse_time(chunk['time_stamp'])
|
21
|
+
step = chunk['time_delta'].to_i
|
22
|
+
|
23
|
+
# Run each row in the chunk, and yield our results
|
24
|
+
(chunk / './r').each do |row|
|
25
|
+
val = (row / './c')[@index].text.to_i
|
26
|
+
yield ts, val
|
27
|
+
ts += step
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return results as a 2D array, like so: [ [timestamp1, val1], [timestamp2, val2], ... ]
|
33
|
+
def to_a
|
34
|
+
res = []
|
35
|
+
each_with_timestamp do |ts, val|
|
36
|
+
res << [ts, val]
|
37
|
+
end
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
def count
|
42
|
+
@data.num_rows
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
describe EGauge::Data do
|
3
|
+
|
4
|
+
before do
|
5
|
+
# Let's get us a sample to work on...
|
6
|
+
@data1 = EGauge::Data.parse(SAMPLE_FILE['data1'])
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should parse valid xml' do
|
10
|
+
xml = SAMPLE_FILE['data1']
|
11
|
+
group = EGauge::Data.parse(xml)
|
12
|
+
group.should_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should provide the serial number' do
|
16
|
+
@data1.serial.should == '0x37cdd096'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should provide the timestamp' do
|
20
|
+
@data1.timestamp.should == Time.at(0x4c9197e4)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should provide a count of the registers' do
|
24
|
+
@data1.num_registers.should == 3
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should let you access registers as an array' do
|
28
|
+
@data1.should respond_to(:registers)
|
29
|
+
@data1.registers.should be_a(Array)
|
30
|
+
@data1.registers[2].should be_a(EGauge::Register)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should know the number of data rows' do
|
34
|
+
@data1.num_rows.should == 3
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should allow access to full row data' do
|
38
|
+
@data1.each_with_timestamp do |ts, values|
|
39
|
+
ts.should be_a(Time)
|
40
|
+
values.should be_a(Array)
|
41
|
+
values.count.should == 3
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
describe EGauge::Register do
|
3
|
+
|
4
|
+
before do
|
5
|
+
@data1 = EGauge::Data.parse(SAMPLE_FILE['data1'])
|
6
|
+
@reg = @data1.registers[1]
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should provide a label' do
|
10
|
+
@reg.label.should == 'Solar'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should provide a type code' do
|
14
|
+
@reg.type_code.should == 'P'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should know its own index' do
|
18
|
+
@reg.index.should == 1
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return valid values' do
|
22
|
+
@reg.to_a.collect {|row| row[1]}.should == [21308125431, 21308125526, 21308125626]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should allow iterating over values with timestamps' do
|
26
|
+
counter = 0
|
27
|
+
@reg.each_with_timestamp do |ts, val|
|
28
|
+
val.should be_a(Integer)
|
29
|
+
ts.should be_a(Time)
|
30
|
+
counter += 1
|
31
|
+
end
|
32
|
+
counter.should == 3
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should know the number of values it owns' do
|
36
|
+
@reg.count
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
2
|
+
<group serial="0x37cdd096">
|
3
|
+
<data columns="3" time_stamp="0x4c9197e4" time_delta="60" epoch="0x47395980">
|
4
|
+
<cname t="P">Grid</cname>
|
5
|
+
<cname t="P">Solar</cname>
|
6
|
+
<cname t="P">Grg&Bth (PHEV)</cname>
|
7
|
+
<r><c>5203642184</c><c>21308125431</c><c>17598056700</c></r>
|
8
|
+
<r><c>5203503484</c><c>21308125526</c><c>17598116405</c></r>
|
9
|
+
<r><c>5203368999</c><c>21308125626</c><c>17598176060</c></r>
|
10
|
+
</data>
|
11
|
+
</group>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
2
|
+
<!DOCTYPE group PUBLIC "-//ESL/DTD eGauge 1.0//EN" "http://www.egauge.net/DTD/egauge-hist.dtd">
|
3
|
+
<group serial="0x64bf2a22">
|
4
|
+
<data columns="17" time_stamp="0x505b93c0" time_delta="60" epoch="0x50573af0">
|
5
|
+
<cname t="P">use</cname>
|
6
|
+
<cname t="P">gen</cname>
|
7
|
+
<cname t="P">Gen Virt</cname>
|
8
|
+
<cname t="#">aaron.testv1</cname>
|
9
|
+
<cname t="P">aaron.vtest2</cname>
|
10
|
+
<cname t="#">reac</cname>
|
11
|
+
<cname t="P">Mayo</cname>
|
12
|
+
<cname t="P">Mayo+</cname>
|
13
|
+
<cname t="S">Mayo*</cname>
|
14
|
+
<cname t="#">catchup ¥</cname>
|
15
|
+
<cname t="P">Register 1-</cname>
|
16
|
+
<cname t="P">Panel HP</cname>
|
17
|
+
<cname t="P">Panel PA</cname>
|
18
|
+
<cname t="P">Panel PB</cname>
|
19
|
+
<cname t="P">Panel LP</cname>
|
20
|
+
<cname t="P">solar2</cname>
|
21
|
+
<cname t="P">solar1</cname>
|
22
|
+
<r><c>745905872565</c><c>0</c><c>51140761685</c><c>1702828296559</c><c>-11291842883</c><c>193</c><c>-11309952897</c><c>-11291842883</c><c>1041</c><c>1702828296559</c><c>0</c><c>-50610360087</c><c>-486918628116</c><c>-533588476436</c><c>-695295512478</c><c>25077777223</c><c>26062984462</c></r>
|
23
|
+
</data>
|
24
|
+
</group>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
2
|
+
<group serial="0x37cdd096">
|
3
|
+
<data columns="1" time_stamp="0x4c9197e4" time_delta="60" epoch="0x47395980">
|
4
|
+
<cname t="P">Grid</cname>
|
5
|
+
<r><c>123</c></r>
|
6
|
+
<r><c>456</c></r>
|
7
|
+
</data>
|
8
|
+
<data columns="1" time_stamp="0x4c9197e4" time_delta="300" epoch="0x47395980">
|
9
|
+
<cname t="P">Grid</cname>
|
10
|
+
<r><c>789</c></r>
|
11
|
+
<r><c>101112</c></r>
|
12
|
+
</data>
|
13
|
+
</group>
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Require our library
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'egauge'))
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
#config.add_formatter 'documentation'
|
6
|
+
config.color = true
|
7
|
+
config.backtrace_clean_patterns = [/rspec/]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Set up some sample files
|
11
|
+
SAMPLE_FILE = {}
|
12
|
+
Dir.glob(File.join(File.dirname(__FILE__), './samples/*.xml')).each do |p|
|
13
|
+
SAMPLE_FILE[File.basename(p,'.xml')] = File.read(p)
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: egauge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rob Morris
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.6'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.6'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: nokogiri
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Allows accepting and parsing eGauge Energy monitor xml data as native
|
47
|
+
Ruby objects.
|
48
|
+
email:
|
49
|
+
- rob@irongaze.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- lib/egauge/data.rb
|
55
|
+
- lib/egauge/register.rb
|
56
|
+
- lib/egauge.rb
|
57
|
+
- spec/egauge/data_spec.rb
|
58
|
+
- spec/egauge/egauge_spec.rb
|
59
|
+
- spec/egauge/register_spec.rb
|
60
|
+
- spec/samples/data1.xml
|
61
|
+
- spec/samples/data2.xml
|
62
|
+
- spec/samples/data3.xml
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
- LICENSE
|
65
|
+
- History.txt
|
66
|
+
- Version.txt
|
67
|
+
- README.rdoc
|
68
|
+
- .rspec
|
69
|
+
homepage: http://irongaze.com
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 1.9.2
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.8.24
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: Data stream parser for eGauge monitoring output
|
94
|
+
test_files: []
|