simms 1.0.0

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/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require <%= File.join(File.expand_path(File.dirname(__FILE__)), 'spec', 'spec_helper.rb') %>
@@ -0,0 +1,7 @@
1
+ == 1.0.0 / 2012-03-02
2
+
3
+ * Initial revision
4
+ * Support for stream bytes parsing
5
+ * Support for packet parsing
6
+ * Support for global config, electricity config and electricity data beacons
7
+ * Full rspec coverage
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.
@@ -0,0 +1,60 @@
1
+ = GEM: simms
2
+
3
+ Written by Rob Morris @ Irongaze Consulting LLC (http://irongaze.com)
4
+
5
+ Based on sample PHP code and documentation provided by Simms Energy (http://simmsenergy.com)
6
+
7
+ Funding for development provided by Sustainable Industrial Solutions LLC (http://sustainableis.com)
8
+
9
+ == DESCRIPTION
10
+
11
+ A set of classes to enable reading posted data from Simms Energy monitoring products (http://simmsenergy.com)
12
+
13
+ == SYNOPSIS
14
+
15
+ This gem facilitates interpreting incoming monitoring data and converting it into Ruby objects for easier
16
+ manipulation. It handles parsing the byte-stream of mesh data Simms devices provide, including
17
+ conversion of hex byte-codes into Ruby native data types such as integers, bools and floats.
18
+
19
+ To use:
20
+
21
+ # Require the library (not needed if using Rails/bundler)
22
+ >> require 'simms'
23
+
24
+ # Parse a packet to extract the contained beacon, passing in
25
+ # the timestamp and data posted to your server from the Simms device
26
+ >> beacon = Simms::Packet.parse(params['DateTime'], params['MeshData'])
27
+
28
+ # Which device sent this beacon?
29
+ >> beacon.uuid
30
+ => '0A-28-00-48-27-88'
31
+
32
+ # See what kind of beacon we have
33
+ >> beacon.group
34
+ => :electric
35
+ >> beacon.type
36
+ => :data
37
+ >> beacon.data?
38
+ => true
39
+
40
+ # Extract some data from the beacon
41
+ >> beacon.phase
42
+ => 0
43
+ >> beacon.accumulated_real_energy
44
+ => 15290
45
+ >> beacon.present_real_power
46
+ => 472.08821
47
+
48
+ == REQUIREMENTS
49
+
50
+ None, though you'll need rspec to build and test the gem
51
+
52
+ == INSTALL
53
+
54
+ To install, simply run:
55
+
56
+ sudo gem install simms
57
+
58
+ RVM users can skip the sudo:
59
+
60
+ gem install simms
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,81 @@
1
+ # Represents the data or configuration information broadcast by a single Simms monitoring
2
+ # device.
3
+ module Simms
4
+ class Beacon
5
+
6
+ # Attributes
7
+ attr_accessor :uuid, :timestamp, :command_group_id, :device_command_id
8
+
9
+ # Command group codes
10
+ CG_GLOBAL = 0xFFFF
11
+ CG_SENSOR = 0x0001
12
+ CG_ELECTRIC = 0x0002
13
+ CG_HVAC = 0x0004
14
+ CG_PULSER = 0x0005
15
+ CG_IRRIGATION = 0x0007
16
+
17
+ # Device commands
18
+ DC_DATA = 0x0001
19
+ DC_GLOBAL_CONFIG = 0x000B
20
+ DC_CONFIG = 0x0022
21
+
22
+ # Find the appropriate beacon class for a given group + command tuple
23
+ def self.class_for(group, command)
24
+ case group
25
+
26
+ when CG_GLOBAL then
27
+ case command
28
+ when DC_GLOBAL_CONFIG then
29
+ return Simms::GlobalConfigBeacon
30
+ end
31
+
32
+ when CG_ELECTRIC then
33
+ case command
34
+ when DC_DATA then
35
+ return Simms::ElectricityDataBeacon
36
+ when DC_CONFIG then
37
+ return Simms::ElectricityConfigBeacon
38
+ end
39
+
40
+ end
41
+
42
+ # None found!
43
+ nil
44
+ end
45
+
46
+ def initialize(bytes)
47
+ @bytes = bytes
48
+ end
49
+
50
+ def group
51
+ case @command_group_id
52
+ when CG_GLOBAL then :global
53
+ when CG_SENSOR then :sensor
54
+ when CG_ELECTRIC then :electric
55
+ when CG_HVAC then :hvac
56
+ when CG_PULSER then :pulser
57
+ when CG_IRRIGATION then :irrigation
58
+ else
59
+ :unknown
60
+ end
61
+ end
62
+
63
+ def type
64
+ case @device_command_id
65
+ when DC_DATA then :data
66
+ when DC_CONFIG, DC_GLOBAL_CONFIG then :config
67
+ else
68
+ :unknown
69
+ end
70
+ end
71
+
72
+ def data?
73
+ type == :data
74
+ end
75
+
76
+ def config?
77
+ type == :config
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,73 @@
1
+ # Manages a set of byte codes in hex string format (eg 'AF', '0C', etc) and
2
+ # provides methods for extracting floats, ints, etc from that stream by
3
+ # position.
4
+ #
5
+ # Responsible for low-level translation of incoming data into types usable
6
+ # in Ruby.
7
+ module Simms
8
+ class Bytes
9
+
10
+ attr_accessor :data
11
+
12
+ def initialize(byte_array)
13
+ @data = byte_array
14
+ @data = @data.to_s.scan(/../) unless @data.is_a?(Array)
15
+ end
16
+
17
+ def count
18
+ @data.count
19
+ end
20
+
21
+ def empty?
22
+ count == 0
23
+ end
24
+
25
+ def uint8(pos)
26
+ @data[pos].hex
27
+ end
28
+
29
+ def uint16(pos)
30
+ @data[pos...pos+2].reverse.join('').hex
31
+ end
32
+
33
+ def sint16(pos)
34
+ [@data[pos...pos+2].join].pack('H*').unpack('s').first
35
+ end
36
+
37
+ def uint32(pos)
38
+ @data[pos...pos+4].reverse.join('').hex
39
+ end
40
+
41
+ def sint32(pos)
42
+ [@data[pos...pos+4].join].pack('H*').unpack('l').first
43
+ end
44
+
45
+ def bool(pos)
46
+ @data[pos].hex == 1
47
+ end
48
+
49
+ def float(pos)
50
+ v = uint32(pos)
51
+ x = (v & ((1 << 23) - 1)) + (1 << 23) * (v >> 31 | 1)
52
+ exp = (v >> 23 & 0xFF) - 127
53
+ return x.to_f * (2 ** (exp - 23))
54
+ end
55
+
56
+ def slice(*args)
57
+ Bytes.new(@data.slice(*args))
58
+ end
59
+
60
+ def [](*args)
61
+ Bytes.new(@data[*args])
62
+ end
63
+
64
+ def calc_checksum
65
+ @data.inject(0) {|sum, byte| sum + byte.hex} % 0xFF
66
+ end
67
+
68
+ def to_s
69
+ @data.join('')
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,34 @@
1
+ require 'simms/beacon'
2
+
3
+ module Simms
4
+ class ElectricityConfigBeacon < Simms::Beacon
5
+
6
+ def ct_amps
7
+ @bytes.uint16(6)
8
+ end
9
+
10
+ def nominal_voltage
11
+ @bytes.uint16(8)
12
+ end
13
+
14
+ def phase_type
15
+ @bytes.uint8(10)
16
+ end
17
+
18
+ # Human-readable description of the phase type
19
+ def phase_type_description
20
+ case phase_type
21
+ when 0 then
22
+ '3 Phase With Neutral, Display L-N'
23
+ when 1 then
24
+ '3 Phase With Neutral, Display L-L'
25
+ when 2 then
26
+ 'Single Split Phase A+B with Neutral, Display L-N'
27
+ when 3 then
28
+ 'Single Phase A to Neutral, Display L-N'
29
+ else
30
+ 'Unknown phase type!'
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,70 @@
1
+ require 'simms/beacon'
2
+
3
+ # Electricity data beacons are sent at the time interval configured in the device, and will be
4
+ # have one packet sent per phase being monitored, ie for a 3-phase system, you will receive
5
+ # 3 data beacons per reporting interval per device, with the phase field set to 0, 1, or 2.
6
+ module Simms
7
+ class ElectricityDataBeacon < Simms::Beacon
8
+
9
+ # Which phase this beacon represents
10
+ def phase
11
+ @bytes.uint8(6)
12
+ end
13
+
14
+ # TODO What does this mean? What are the valid values?
15
+ def error_state
16
+ @bytes.uint8(7)
17
+ end
18
+
19
+ # May be negative, as in the case of a solar power installation. Is a counter, ie to get
20
+ # the real energy (Wh) for a given interval, you need to diff against past values
21
+ def accumulated_real_energy
22
+ @bytes.sint32(8)
23
+ end
24
+
25
+ # In Wh - see #accumulated_real_energy for counter info
26
+ def accumulated_apparent_energy
27
+ @bytes.sint32(12)
28
+ end
29
+
30
+ # In W
31
+ def present_real_power
32
+ @bytes.float(16)
33
+ end
34
+
35
+ # In W
36
+ def present_apparent_power
37
+ @bytes.float(20)
38
+ end
39
+
40
+ def volts_rms
41
+ @bytes.float(24)
42
+ end
43
+
44
+ def current_rms
45
+ @bytes.float(28)
46
+ end
47
+
48
+ # In VA
49
+ def power_factor
50
+ @bytes.float(32)
51
+ end
52
+
53
+ def max_demand
54
+ @bytes.float(36)
55
+ end
56
+
57
+ def volts_sag
58
+ @bytes.uint16(40)
59
+ end
60
+
61
+ def volts_peak
62
+ @bytes.uint16(42)
63
+ end
64
+
65
+ def current_peak
66
+ @bytes.uint16(44)
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,29 @@
1
+ require 'simms/beacon'
2
+
3
+ module Simms
4
+ class GlobalConfigBeacon < Simms::Beacon
5
+
6
+ def version_high
7
+ @bytes.uint16(6)
8
+ end
9
+
10
+ def version_low
11
+ @bytes.uint16(8)
12
+ end
13
+
14
+ def firmware_version
15
+ "#{version_high}.#{version_low}"
16
+ end
17
+
18
+ def local_devices
19
+ @bytes.uint16(10)
20
+ end
21
+
22
+ def device_name
23
+ name = ''
24
+ @bytes.slice(24,16).data.collect {|b| b.hex}.each {|c| name << c if c > 0}
25
+ name
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ module Simms
2
+ class Packet
3
+
4
+ # Action type constants
5
+ ACTION_BEACON = 0x35
6
+
7
+ # Parses an incoming packet, extracts its enclosed beacon data, instantiates
8
+ # the correct beacon class, and returns it. Raises exceptions on malformed data.
9
+ # If beacon type is unknown, raises an exception if raise_on_unknown is set to true. Otherwise,
10
+ # unknown beacons are returned as nil.
11
+ def self.parse(timestamp, data_packet, raise_on_unknown = false)
12
+ # Parse the string into hex pair bytes for the mesh packet
13
+ mesh_data = Bytes.new(data_packet)
14
+
15
+ # Extract the bytes for the embedded beacon data
16
+ beacon_data = mesh_data[34..-1]
17
+
18
+ # Verify data
19
+ if mesh_data.empty? || beacon_data.empty?
20
+ raise "Malformed packet - missing segments"
21
+ end
22
+ unless mesh_data.uint16(32) == beacon_data.count
23
+ raise "Malformed packet - length field (#{mesh_data.uint16(32)}) does not match data length (#{beacon_data.count})"
24
+ end
25
+
26
+ # Parse timestamp - always UTC time
27
+ timestamp = Time.utc(*(timestamp.split(/[\- \:]/)))
28
+
29
+ # Get key variables from packet
30
+ uuid = mesh_data.slice(6,6).data.join('-')
31
+ group = beacon_data.uint16(0)
32
+ command = beacon_data.uint8(2)
33
+ action = beacon_data.uint8(3)
34
+
35
+ # Instantiate beacon instance (assuming it's a beacon action!)
36
+ klass = Simms::Beacon.class_for(group, command) if action == ACTION_BEACON
37
+ if klass
38
+ # Create the beacon, then set all the meta-info we have about it
39
+ beacon = klass.new(beacon_data)
40
+ beacon.command_group_id = group
41
+ beacon.device_command_id = command
42
+ beacon.timestamp = timestamp
43
+ beacon.uuid = uuid
44
+ beacon
45
+
46
+ else
47
+ raise "Unable to find beacon class for group #{group} and command #{command}" if raise_on_unknown
48
+ nil
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,136 @@
1
+
2
+ describe Simms::Beacon do
3
+
4
+ SAMPLE_TIMESTAMP = '2012-06-27 16:16:08'
5
+ SAMPLE_GC_BEACON = 'FB10040002000015ABA0B0220100FF00FF00FFFFFFFFFFFF0100790E937900022A00FFFF0B351A820000C8000100040002000015ABA0B022010053696D6D734F666669636550616E656C0000'
6
+ SAMPLE_EC_BEACON = 'FB10040002000015ABA0B0250100FF00FF00FFFFFFFFFFFF0100AA72704C00020D00020022359FCA960078000300D3'
7
+ SAMPLE_ED_BEACON = 'FB10040002000015ABA0B0220100FF00FF00FFFFFFFFFFFF01008E51CB1C00022E0002000135E58900014EF722004308280041E91144132620442D5EF542D417A740B43D693F8C8820447A007A000500'
8
+
9
+ it 'should return the correct class for a given group + command combo' do
10
+ {
11
+ [0xFFFF, 0x0B] => Simms::GlobalConfigBeacon,
12
+ [0x0002, 0x01] => Simms::ElectricityDataBeacon,
13
+ [0x0002, 0x22] => Simms::ElectricityConfigBeacon
14
+ }.each do |info, klass|
15
+ Simms::Beacon.class_for(*info).should == klass
16
+ end
17
+ end
18
+
19
+ context 'when a global configuration beacon' do
20
+ before do
21
+ @beacon = Simms::Packet.parse(SAMPLE_TIMESTAMP, SAMPLE_GC_BEACON)
22
+ end
23
+
24
+ it 'should know what kind of beacon it is' do
25
+ @beacon.group.should == :global
26
+ @beacon.type.should == :config
27
+ @beacon.config?.should be_true
28
+ end
29
+
30
+ it 'should extract firmware version' do
31
+ @beacon.firmware_version.should == '0.200'
32
+ end
33
+
34
+ it 'should extract local device count' do
35
+ @beacon.local_devices.should == 1
36
+ end
37
+
38
+ it 'should extract device name' do
39
+ @beacon.device_name.should == 'SimmsOfficePanel'
40
+ end
41
+ end
42
+
43
+ context 'when an electricity configuration beacon' do
44
+ before do
45
+ @beacon = Simms::Packet.parse(SAMPLE_TIMESTAMP, SAMPLE_EC_BEACON)
46
+ end
47
+
48
+ it 'should know what kind of beacon it is' do
49
+ @beacon.group.should == :electric
50
+ @beacon.type.should == :config
51
+ @beacon.config?.should be_true
52
+ end
53
+
54
+ it 'should extract CT clamp amps' do
55
+ @beacon.ct_amps.should == 150
56
+ end
57
+
58
+ it 'should extract nominal voltage' do
59
+ @beacon.nominal_voltage.should == 120
60
+ end
61
+
62
+ it 'should extract phase type' do
63
+ @beacon.phase_type.should == 3
64
+ end
65
+
66
+ it 'should provide a user-readable phase type' do
67
+ @beacon.phase_type_description.should == 'Single Phase A to Neutral, Display L-N'
68
+ end
69
+ end
70
+
71
+ context 'when an electricity data beacon' do
72
+ before do
73
+ @beacon = Simms::Packet.parse(SAMPLE_TIMESTAMP, SAMPLE_ED_BEACON)
74
+ end
75
+
76
+ it 'should know what kind of beacon it is' do
77
+ @beacon.group.should == :electric
78
+ @beacon.type.should == :data
79
+ @beacon.data?.should be_true
80
+ end
81
+
82
+ it 'should extract the phase' do
83
+ @beacon.phase.should == 0
84
+ end
85
+
86
+ it 'should extract the error state' do
87
+ @beacon.error_state.should == 1
88
+ end
89
+
90
+ it 'should extract the accumulated real energy (Wh)' do
91
+ @beacon.accumulated_real_energy.should == 2291534
92
+ end
93
+
94
+ it 'should extract the accumulated apparent energy' do
95
+ @beacon.accumulated_apparent_energy.should == 2623555
96
+ end
97
+
98
+ it 'should extract the present real power' do
99
+ @beacon.present_real_power.round(4).should == 583.6446
100
+ end
101
+
102
+ it 'should extract the present apparent power' do
103
+ @beacon.present_apparent_power.round(4).should == 640.5949
104
+ end
105
+
106
+ it 'should extract the volts rms' do
107
+ @beacon.volts_rms.round(4).should == 122.6839
108
+ end
109
+
110
+ it 'should extract the current rms' do
111
+ @beacon.current_rms.round(4).should == 5.2217
112
+ end
113
+
114
+ it 'should extract the power factor' do
115
+ @beacon.power_factor.round(4).should == 0.9111
116
+ end
117
+
118
+ it 'should extract the max demand' do
119
+ @beacon.max_demand.round(4).should == 642.1335
120
+ end
121
+
122
+ it 'should extract the volts sag' do
123
+ @beacon.volts_sag.should == 122
124
+ end
125
+
126
+ it 'should extract the volts peak' do
127
+ @beacon.volts_peak.should == 122
128
+ end
129
+
130
+ it 'should extract the current peak' do
131
+ @beacon.current_peak.should == 5
132
+ end
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,47 @@
1
+ describe Simms::Bytes do
2
+
3
+ it 'should manage a set of byte strings' do
4
+ bytes = bytes('010200ABCD')
5
+ bytes.count.should == 5
6
+ bytes.data.last.should == 'CD'
7
+ end
8
+
9
+ it 'should extract unsigned 8 bit ints' do
10
+ bytes('01').uint8(0).should == 1
11
+ bytes('AC').uint8(0).should == 172
12
+ bytes('FF').uint8(0).should == 255
13
+ end
14
+
15
+ it 'should extract unsigned 16 bit ints' do
16
+ bytes('0400').uint16(0).should == 4
17
+ bytes('0000').uint16(0).should == 0
18
+ bytes('FFFF').uint16(0).should == 65535
19
+ end
20
+
21
+ it 'should extract unsigned 32 bit ints' do
22
+ bytes('4EF72200').uint32(0).should == 2291534
23
+ bytes('FFFFFFFF').uint32(0).should == 4294967295
24
+ end
25
+
26
+ it 'should extract signed 32 bit ints' do
27
+ bytes('4EF72200').sint32(0).should == 2291534
28
+ bytes('FFFFFFFF').sint32(0).should == -1
29
+ end
30
+
31
+ it 'should extract floats' do
32
+ bytes('8C882044').float(0).round(4).should == 642.1335
33
+ bytes('B43D693F').float(0).round(4).should == 0.9111
34
+ end
35
+
36
+ it 'should enable subset extraction' do
37
+ @bytes = bytes('1234567890')
38
+ @bytes[0].data.should == ['12']
39
+ @bytes[1,2].to_s.should == '3456'
40
+ end
41
+
42
+ # Helper to construct a new Bytes object from a given string
43
+ def bytes(str)
44
+ Simms::Bytes.new(str)
45
+ end
46
+
47
+ end
@@ -0,0 +1,30 @@
1
+ describe Simms::Packet do
2
+
3
+ SAMPLE_TIMESTAMP = '2012-06-27 16:16:08'
4
+ SAMPLE_PACKET = 'FB10040002000015ABA0B0220100FF00FF00FFFFFFFFFFFF0100790E937900022A00FFFF0B351A820000C8000100040002000015ABA0B022010053696D6D734F666669636550616E656C0000'
5
+
6
+ before(:all) do
7
+ @beacon = Simms::Packet.parse(SAMPLE_TIMESTAMP, SAMPLE_PACKET)
8
+ end
9
+
10
+ it 'should parse valid packets' do
11
+ Simms::Packet.parse(SAMPLE_TIMESTAMP, SAMPLE_PACKET).should_not be_nil
12
+ end
13
+
14
+ it 'should return the correct beacon type' do
15
+ @beacon.should be_a(Simms::GlobalConfigBeacon)
16
+ end
17
+
18
+ it 'should correctly set the beacon\'s device UUID' do
19
+ @beacon.uuid.should == '00-15-AB-A0-B0-22'
20
+ end
21
+
22
+ it 'should correctly set the beacon\'s timestamp' do
23
+ @beacon.timestamp.should == Time.utc(2012,6,27,16,16,8)
24
+ end
25
+
26
+ it 'should return raise on invalid packet data' do
27
+ lambda{Simms::Packet.parse(SAMPLE_TIMESTAMP, '1234')}.should raise_error
28
+ end
29
+
30
+ end
@@ -0,0 +1,9 @@
1
+ # Require our library
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'simms'))
3
+
4
+ RSpec.configure do |config|
5
+ #config.add_formatter 'documentation'
6
+ config.color = true
7
+ config.backtrace_clean_patterns = [/rspec/]
8
+ end
9
+
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simms
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-09-18 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
+ description: Allows accepting and parsing Simms Energy monitor data streams.
31
+ email:
32
+ - rob@irongaze.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/simms/beacon.rb
38
+ - lib/simms/bytes.rb
39
+ - lib/simms/electricity_config_beacon.rb
40
+ - lib/simms/electricity_data_beacon.rb
41
+ - lib/simms/global_config_beacon.rb
42
+ - lib/simms/packet.rb
43
+ - lib/simms.rb
44
+ - spec/simms/beacon_spec.rb
45
+ - spec/simms/bytes_spec.rb
46
+ - spec/simms/packet_spec.rb
47
+ - spec/spec_helper.rb
48
+ - LICENSE
49
+ - History.txt
50
+ - Version.txt
51
+ - README.rdoc
52
+ - .rspec
53
+ homepage: http://irongaze.com
54
+ licenses:
55
+ - MIT
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 1.9.2
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 1.8.24
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Data stream parser for Simms Energy monitors
78
+ test_files: []