simms 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 +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +60 -0
- data/Version.txt +1 -0
- data/lib/simms.rb +8 -0
- data/lib/simms/beacon.rb +81 -0
- data/lib/simms/bytes.rb +73 -0
- data/lib/simms/electricity_config_beacon.rb +34 -0
- data/lib/simms/electricity_data_beacon.rb +70 -0
- data/lib/simms/global_config_beacon.rb +29 -0
- data/lib/simms/packet.rb +53 -0
- data/spec/simms/beacon_spec.rb +136 -0
- data/spec/simms/bytes_spec.rb +47 -0
- data/spec/simms/packet_spec.rb +30 -0
- data/spec/spec_helper.rb +9 -0
- metadata +78 -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,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
|
data/Version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/simms.rb
ADDED
data/lib/simms/beacon.rb
ADDED
@@ -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
|
data/lib/simms/bytes.rb
ADDED
@@ -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
|
data/lib/simms/packet.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
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: []
|