ekm-omnimeter 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +58 -0
- data/LICENSE +21 -0
- data/README.md +99 -0
- data/Rakefile +5 -0
- data/ekm-omnimeter.gemspec +32 -0
- data/lib/ekm-omnimeter.rb +8 -0
- data/lib/ekm-omnimeter/logging.rb +25 -0
- data/lib/ekm-omnimeter/meter.rb +359 -0
- data/lib/ekm-omnimeter/version.rb +14 -0
- data/spec/meter_spec.rb +26 -0
- data/spec/spec_helper.rb +42 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 10dccf5f6ac3dcbab08c817d8efe39f9781352d4
|
4
|
+
data.tar.gz: c890f15fa21dae3bee6478505dbde6f366318de0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c78895c1cb06608cd7c23f0a489866c12305bef1532ee610cb8e6e69976868a96b5a16f977e080416dc8bdde0334346eced2ab543e593b4177bc9b04e6913578
|
7
|
+
data.tar.gz: cee6d71ff4faaec035eaae6daa1b798063814e53363a2b1a142ce7a3c5cc189f98324edf6202c87c505f6f5eff10a273991d8564b1a2f57ca337d9c18219dd38
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
# Gemfile.lock
|
30
|
+
# .ruby-version
|
31
|
+
# .ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ekm-omnimeter (0.1.0)
|
5
|
+
bigdecimal
|
6
|
+
daemons
|
7
|
+
log4r (~> 1.1)
|
8
|
+
trollop
|
9
|
+
xively-rb-connector (~> 0.1)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
bigdecimal (1.2.5)
|
15
|
+
daemons (1.1.9)
|
16
|
+
diff-lcs (1.2.5)
|
17
|
+
httparty (0.13.0)
|
18
|
+
json (~> 1.8)
|
19
|
+
multi_xml (>= 0.5.2)
|
20
|
+
json (1.8.1)
|
21
|
+
log4r (1.1.10)
|
22
|
+
mini_portile (0.5.3)
|
23
|
+
multi_json (1.9.2)
|
24
|
+
multi_xml (0.5.5)
|
25
|
+
nokogiri (1.6.1)
|
26
|
+
mini_portile (~> 0.5.0)
|
27
|
+
ox (2.1.1)
|
28
|
+
rake (10.2.2)
|
29
|
+
rspec (2.14.1)
|
30
|
+
rspec-core (~> 2.14.0)
|
31
|
+
rspec-expectations (~> 2.14.0)
|
32
|
+
rspec-mocks (~> 2.14.0)
|
33
|
+
rspec-core (2.14.8)
|
34
|
+
rspec-expectations (2.14.5)
|
35
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
36
|
+
rspec-mocks (2.14.6)
|
37
|
+
trollop (2.0)
|
38
|
+
xively-rb (0.2.10)
|
39
|
+
httparty (>= 0.10.0)
|
40
|
+
multi_json (>= 1.3.6)
|
41
|
+
multi_xml (>= 0.5.2)
|
42
|
+
nokogiri (>= 1.5.6)
|
43
|
+
ox (>= 1.5.9)
|
44
|
+
yajl-ruby (>= 1.1.0)
|
45
|
+
xively-rb-connector (0.1.1)
|
46
|
+
bigdecimal (~> 1.2)
|
47
|
+
log4r (~> 1.1)
|
48
|
+
xively-rb (~> 0.2)
|
49
|
+
yajl-ruby (1.2.0)
|
50
|
+
|
51
|
+
PLATFORMS
|
52
|
+
ruby
|
53
|
+
|
54
|
+
DEPENDENCIES
|
55
|
+
bundler (~> 1.3)
|
56
|
+
ekm-omnimeter!
|
57
|
+
rake (~> 10.2)
|
58
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Jordan Duggan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
ekm-omnimeter
|
2
|
+
=============
|
3
|
+
|
4
|
+
This ruby gem provides an interface to [EKM Omnimeter Pulse meters](http://www.ekmmetering.com/ekm-metering-products/electric-meters-kwh-meters/smart-meters-read-meter-remotely-automatically/omnimeter-pulse.html)
|
5
|
+
that are connected to a [EKM iSerial TCP/IP Ethernet to RS-485 Serial Converter](http://www.ekmmetering.com/ekm-metering-products/remote-meter-reading-solutions/ekm-iserial-v-2-tcp-ip-to-serial-converter-ethernet-connection.html).
|
6
|
+
|
7
|
+
[EKM Metering](http://www.ekmmetering.com) offers a range of digital utility metering and control devices, including the
|
8
|
+
Omnimeter Pulse Meter line of smart meters. The
|
9
|
+
[Omnimeter Pulse Meter](http://www.ekmmetering.com/ekm-metering-products/electric-meters-kwh-meters/smart-meters-read-meter-remotely-automatically/omnimeter-pulse.html)
|
10
|
+
contains a revenue-grade universal kWh power meter, three pulse counting inputs for
|
11
|
+
[water](http://www.ekmmetering.com/ekm-metering-products/water-meters.html) and/or
|
12
|
+
[gas](http://www.ekmmetering.com/ekm-metering-products/gas-meters/pulse-output-gas-meter-pgm-1-read-gas-consumption-remotely.html) metering,
|
13
|
+
and 2 controllable 50 mA at 12 volts relay outputs that can toggle 120v circuits when connected to their [EKM Switch120 power cord](http://www.ekmmetering.com/ekm-metering-products/accessories/switch120.html).
|
14
|
+
|
15
|
+
TODO:
|
16
|
+
* Cast values to their proper type and precision
|
17
|
+
* Add specs
|
18
|
+
* Add daemon to monitor meter's output at regular intervals
|
19
|
+
|
20
|
+
NOTE:
|
21
|
+
## Requirements
|
22
|
+
|
23
|
+
* Ruby 2.0.0 or higher
|
24
|
+
* A [EKM Omnimeter Pulse meters](http://www.ekmmetering.com/ekm-metering-products/electric-meters-kwh-meters/smart-meters-read-meter-remotely-automatically/omnimeter-pulse.html)
|
25
|
+
* A [EKM iSerial TCP/IP Ethernet to RS-485 Serial Converter](http://www.ekmmetering.com/ekm-metering-products/remote-meter-reading-solutions/ekm-iserial-v-2-tcp-ip-to-serial-converter-ethernet-connection.html)
|
26
|
+
|
27
|
+
|
28
|
+
## Contact, feedback and bugs
|
29
|
+
|
30
|
+
This interface was not developed or reviewed by EKM Metering. They bare no responsibility for its quality, performance, or results. Use at your own risk.
|
31
|
+
|
32
|
+
Please file bugs / issues and feature requests on the [issue tracker](https://github.com/jwtd/ekm-omnimeter/issues)
|
33
|
+
|
34
|
+
## Install
|
35
|
+
|
36
|
+
```
|
37
|
+
gem install ekm-omnimeter
|
38
|
+
```
|
39
|
+
|
40
|
+
## Examples
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
|
44
|
+
require 'ekm-omnimeter'
|
45
|
+
|
46
|
+
# Connect to the meter
|
47
|
+
m = EkmOmnimeter::Meter.new(
|
48
|
+
:power_configuration => :single_phase_3wire, # Valid values are :single_phase_2wire, :single_phase_3wire, :three_phase_3wire, :three_phase_4wire
|
49
|
+
:meter_number=>300000001, # Your nine digit meter id
|
50
|
+
:remote_address=>'192.168.1.125', # The IP address of your iSerial device
|
51
|
+
:remote_port => 50000) # The port on which your iSerial device is listening
|
52
|
+
|
53
|
+
# Read some values
|
54
|
+
m.remote_address # 192.168.1.125
|
55
|
+
m.remote_port # 50000
|
56
|
+
m.meter_number # 000300000001
|
57
|
+
m.address # 000300000001
|
58
|
+
m.measurement_timestamp # 2014-04-03 05:03:01
|
59
|
+
m.total_active_kwh # 00450609
|
60
|
+
m.total_kvarh # 00080417
|
61
|
+
m.total_rev_kwh # 00007590
|
62
|
+
m.three_phase_kwh # 002115660023898000000000
|
63
|
+
m.three_phase_rev_kwh # 000000000000759000000000
|
64
|
+
m.resettable_kwh # 00450609
|
65
|
+
m.resettable_reverse_kwh # 00007590
|
66
|
+
m.volts_l1 # 1245
|
67
|
+
m.volts_l2 # 1254
|
68
|
+
m.volts_l3 # 0000
|
69
|
+
m.amps_l1 # 00200
|
70
|
+
m.amps_l2 # 00056
|
71
|
+
m.amps_l3 # 00000
|
72
|
+
m.watts_l1 # 0002504
|
73
|
+
m.watts_l2 # 0000664
|
74
|
+
m.watts_l3 # 0000000
|
75
|
+
m.watts_total # 0003172
|
76
|
+
m.cosϴ_l1 # 100
|
77
|
+
m.cosϴ_l2 # 100
|
78
|
+
m.cosϴ_l3 # C000
|
79
|
+
m.var_l1 # 0000052
|
80
|
+
m.var_l2 # 0000000
|
81
|
+
m.var_l3 # 0000000
|
82
|
+
m.var_total # 0000052
|
83
|
+
m.freq # 6006
|
84
|
+
m.pulse_count_1 # 00000003
|
85
|
+
m.pulse_count_2 # 00000006
|
86
|
+
m.pulse_count_3 # 00000000
|
87
|
+
m.pulse_input_hilo # 4
|
88
|
+
m.direction_of_current # 1
|
89
|
+
m.outputs_onoff # 1
|
90
|
+
m.kwh_data_decimal_places # 2
|
91
|
+
|
92
|
+
|
93
|
+
```
|
94
|
+
|
95
|
+
## Special Thanks
|
96
|
+
|
97
|
+
* EKM Metering - for an awesome piece of hardware
|
98
|
+
* Ian Duggan - for introducing me to ruby
|
99
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ekm-omnimeter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ekm-omnimeter"
|
8
|
+
spec.version = EkmOmnimeter::VERSION::STRING
|
9
|
+
spec.authors = ["Jordan Duggan"]
|
10
|
+
spec.email = ["Jordan.Duggan@gmail.com"]
|
11
|
+
spec.description = %q{Ruby interface to the EKM Omnimeter Pulse}
|
12
|
+
spec.summary = %q{This ruby gem provides an interface to EKM Omnimeter Pulse meters that are connected to a EKM iSerial TCP/IP Ethernet to RS-485 Serial Converter. Omnimeter Pulse meters contain a revenue-grade universal kWh power meter, three pulse counting inputs for water and/or gas metering, and 2 controllable 50 mA at 12 volts relay outputs.}
|
13
|
+
spec.homepage = "https://github.com/jwtd/ekm-omnimeter"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# Development dependencies
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.2"
|
24
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
25
|
+
|
26
|
+
# Runtime dependencies
|
27
|
+
spec.add_runtime_dependency "log4r", "~> 1.1"
|
28
|
+
spec.add_runtime_dependency "xively-rb-connector", "~> 0.1"
|
29
|
+
spec.add_runtime_dependency "bigdecimal", "~> 1.2"
|
30
|
+
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
include Log4r
|
3
|
+
module EkmOmnimeter
|
4
|
+
|
5
|
+
module Logging
|
6
|
+
|
7
|
+
def logger
|
8
|
+
@logger ||= EkmOmnimeter::Logging.logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.logger
|
12
|
+
@logger ||= self.configure_logger_for(self.class.name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configure_logger_for(classname)
|
16
|
+
l = Logger.new(classname)
|
17
|
+
l.level = ERROR
|
18
|
+
l.trace = false
|
19
|
+
l.add Log4r::Outputter.stderr
|
20
|
+
l
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,359 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
# Convert byte array to string
|
5
|
+
# %w(01 52 31 02 30 30 31 31 28 29 03 13 16).map{|a| a.to_i(16).chr}.join
|
6
|
+
|
7
|
+
module EkmOmnimeter
|
8
|
+
# EKM Omnimeter Pulse v.4 - Pulse Counting, Relay Controlling, Universal Smart Electric Meter
|
9
|
+
# EKM Developer Portal http://www.ekmmetering.com/developer-portal
|
10
|
+
# PHP Client http://forum.ekmmetering.com/viewtopic.php?f=4&t=3206
|
11
|
+
class Meter
|
12
|
+
|
13
|
+
VALID_POWER_CONFIGURATIONS = [:single_phase_2wire, :single_phase_3wire, :three_phase_3wire, :three_phase_4wire]
|
14
|
+
|
15
|
+
# Initialization attributes
|
16
|
+
attr_reader :meter_number, :remote_address, :remote_port, :power_configuration
|
17
|
+
|
18
|
+
# Request A
|
19
|
+
#attr_reader :meter_type, :meter_firmware, :address, :total_active_kwh, :total_kvarh, :total_rev_kwh, :three_phase_kwh, :three_phase_rev_kwh, :resettable_kwh, :resettable_reverse_kwh, :volts_l1, :volts_l2, :volts_l3, :amps_l1, :amps_l2, :amps_l3, :watts_l1, :watts_l2, :watts_l3, :watts_total, :cosϴ_l1, :cosϴ_l2, :cosϴ_l3, :var_l1, :var_l2, :var_l3, :var_total, :freq, :pulse_count_1, :pulse_count_2, :pulse_count_3, :pulse_input_hilo, :direction_of_current, :outputs_onoff, :kwh_data_decimal_places,
|
20
|
+
|
21
|
+
# Request B
|
22
|
+
#attr_reader :t1_t2_t3_t4_kwh, :t1_t2_t3_t4_rev_kwh, :maximum_demand, :maximum_demand_time, :pulse_ratio_1, :pulse_ratio_2, :pulse_ratio_3, :ct_ratio, :auto_reset_md, :settable_imp_per_kWh_constant
|
23
|
+
|
24
|
+
# Mix in the ability to log
|
25
|
+
include Logging
|
26
|
+
|
27
|
+
def initialize(options)
|
28
|
+
|
29
|
+
@logger = logger || options[:logger]
|
30
|
+
|
31
|
+
# Test logging call
|
32
|
+
@logger.info "Initializing Meter"
|
33
|
+
|
34
|
+
# Prepend the meter number with the correct amount of leading zeros
|
35
|
+
@meter_number = options[:meter_number].to_s.rjust(12, '0')
|
36
|
+
@remote_address = options[:remote_address] || '192.168.0.125'
|
37
|
+
@remote_port = options[:remote_port] || 50000
|
38
|
+
@logger.debug "meter_number: #{meter_number}"
|
39
|
+
@logger.debug "remote_address: #{remote_address}"
|
40
|
+
@logger.debug "remote_port: #{remote_port}"
|
41
|
+
|
42
|
+
# Collect the power configurations
|
43
|
+
if VALID_POWER_CONFIGURATIONS.index(options[:power_configuration])
|
44
|
+
power_configuration = options[:power_configuration]
|
45
|
+
else
|
46
|
+
raise EkmOmnimeterError, "Invalid power configuration #{options[:power_configuration]}. Valid values are #{VALID_POWER_CONFIGURATIONS.join(', ')}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Collect pulse inputs
|
50
|
+
#:ekm_gas_meter
|
51
|
+
#:ekm_water_meter
|
52
|
+
#@pulse_input_1_device = options[:pulse_input_1_device] || nil
|
53
|
+
#@pulse_input_2_device = options[:pulse_input_3_device] || nil
|
54
|
+
#@pulse_input_3_device = options[:pulse_input_2_device] || nil
|
55
|
+
|
56
|
+
@values = {}
|
57
|
+
@last_update = nil
|
58
|
+
|
59
|
+
# Get values
|
60
|
+
request_a()
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# Alias request_a with read
|
65
|
+
def read
|
66
|
+
request_a()
|
67
|
+
end
|
68
|
+
|
69
|
+
# Formatted datetime reported by meter during last read
|
70
|
+
def measurement_timestamp
|
71
|
+
"20#{current_time[0,2]}-#{current_time[2,2]}-#{current_time[4,2]} #{current_time[6,2]}:#{current_time[ 8,2]}:#{current_time[10,2]}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Attribute handler that delegates attribute reads to the values hash
|
75
|
+
def method_missing(method_sym, *arguments, &block)
|
76
|
+
|
77
|
+
@logger.debug "method_missing #{method_sym.inspect}"
|
78
|
+
|
79
|
+
# Only refresh data if its more than 0.25 seconds old
|
80
|
+
et = @last_update.nil? ? 0 : (Time.now - @last_update)
|
81
|
+
@logger.debug "Elapsed time since last read #{et}"
|
82
|
+
if et > 250
|
83
|
+
@logger.info "More than 250 milliseconds have passed, updating data"
|
84
|
+
read()
|
85
|
+
end
|
86
|
+
|
87
|
+
if @values.include? method_sym
|
88
|
+
@logger.debug "Found #{method_sym}"
|
89
|
+
@values[method_sym]
|
90
|
+
else
|
91
|
+
@logger.debug "Didn't find #{method_sym}"
|
92
|
+
super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Attribute responder that delegates check of attribute existence to the values hash
|
97
|
+
def respond_to?(method_sym, include_private = false)
|
98
|
+
if @values.include? method_sym
|
99
|
+
true
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
## Request A
|
107
|
+
#d[:meter_type] # 2 Byte Meter Type
|
108
|
+
#d[:meter_firmware] # 1 Byte Meter Firmware
|
109
|
+
#d[:address] # 12 Bytes Address
|
110
|
+
#d[:total_active_kwh] # 8 Bytes total Active kWh
|
111
|
+
#d[:total_kvarh] # 8 Bytes Total kVARh
|
112
|
+
#d[:total_rev_kwh] # 8 Bytes Total Rev.kWh
|
113
|
+
#d[:three_phase_kwh] # 24 Bytes 3 phase kWh
|
114
|
+
#d[:three_phase_rev_kwh] # 24 Bytes 3 phase Rev.kWh
|
115
|
+
#d[:resettable_kwh] # 8 Bytes Resettable kWh
|
116
|
+
#d[:resettable_reverse_kwh] # 8 bytes Resettable Reverse kWh
|
117
|
+
#d[:volts_l1] # 4 Bytes Volts L1
|
118
|
+
#d[:volts_l2] # 4 Bytes Volts L2
|
119
|
+
#d[:volts_l3] # 4 Bytes Volts L3
|
120
|
+
#d[:amps_l1] # 5 Bytes Amps L1
|
121
|
+
#d[:amps_l2] # 5 Bytes Amps L2
|
122
|
+
#d[:amps_l3] # 5 Bytes Amps L3
|
123
|
+
#d[:watts_l1] # 7 Bytes Watts L1
|
124
|
+
#d[:watts_l2] # 7 Bytes Watts L2
|
125
|
+
#d[:watts_l3] # 7 Bytes Watts L3
|
126
|
+
#d[:watts_total] # 7 Bytes Watts Total
|
127
|
+
#d[:cosϴ_l1] # 4 Bytes Cosϴ L1
|
128
|
+
#d[:cosϴ_l2] # 4 Bytes Cosϴ L2
|
129
|
+
#d[:cosϴ_l3] # 4 Bytes Cosϴ L3
|
130
|
+
#d[:var_l1] # 7 Bytes VAR L1
|
131
|
+
#d[:var_l2] # 7 Bytes VAR L2
|
132
|
+
#d[:var_l3] # 7 Bytes VAR L3
|
133
|
+
#d[:var_total] # 7 Bytes VAR Total
|
134
|
+
#d[:freq] # 4 Bytes Freq
|
135
|
+
#d[:pulse_count_1] # 8 Bytes Pulse Count 1
|
136
|
+
#d[:pulse_count_2] # 8 Bytes Pulse Count 2
|
137
|
+
#d[:pulse_count_3] # 8 Bytes Pulse Count 3
|
138
|
+
#d[:pulse_input_hilo] # 1 Byte Pulse Input Hi/Lo
|
139
|
+
#d[:direction_of_current] # 1 Bytes direction of current
|
140
|
+
#d[:outputs_onoff] # 1 Byte Outputs On/Off
|
141
|
+
#d[:kwh_data_decimal_places] # 1 Byte kWh Data Decimal Places
|
142
|
+
|
143
|
+
## Request B
|
144
|
+
#d[:t1_t2_t3_t4_kwh] # 32 Bytes T1, T2, T3, T4 kwh
|
145
|
+
#d[:t1_t2_t3_t4_rev_kwh] # 32 Bytes T1, T2, T3, T4 Rev kWh
|
146
|
+
#d[:maximum_demand] # 8 Bytes Maximum Demand
|
147
|
+
#d[:maximum_demand_time] # 1 Byte Maximum Demand Time
|
148
|
+
#d[:pulse_ratio_1] # 4 Bytes Pulse Ratio 1
|
149
|
+
#d[:pulse_ratio_2] # 4 Bytes Pulse Ratio 2
|
150
|
+
#d[:pulse_ratio_3] # 4 Bytes Pulse Ratio 3
|
151
|
+
#d[:ct_ratio] # 4 Bytes CT Ratio
|
152
|
+
#d[:auto_reset_md] # 1 Bytes Auto Reset MD
|
153
|
+
#d[:settable_imp_per_kWh_constant] # 4 Bytes Settable Imp/kWh Constant
|
154
|
+
|
155
|
+
|
156
|
+
# iSerial v4 Spec From http://documents.ekmmetering.com/Omnimeter-Pulse-v.4-Protocol.pdf
|
157
|
+
# %w(01 52 31 02 30 30 31 31 28 29 03 13 16).map{|a| a.to_i(16).chr}.join
|
158
|
+
|
159
|
+
# Returns the correct measurement for voltage, current, and power based on the corresponding power_configuration
|
160
|
+
def calculate_measurement(m1, m2, m3)
|
161
|
+
if power_configuration == :single_phase_2wire
|
162
|
+
volts_l1
|
163
|
+
elsif power_configuration == :single_phase_3wire
|
164
|
+
(volts_l1 + volts_l2)
|
165
|
+
elsif power_configuration == :three_phase_3wire
|
166
|
+
(volts_l1 + :volts_l3)
|
167
|
+
elsif power_configuration == :three_phase_4wire
|
168
|
+
(volts_l1 + volts_l2 + volts_l3)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
#Request A:
|
173
|
+
def request_a
|
174
|
+
|
175
|
+
# 2F 3F 12 Bytes Address 30 30 21 0D 0A
|
176
|
+
# /?00000000012300! then a CRLF
|
177
|
+
request = "/?" + meter_number + "00!\r\n"
|
178
|
+
read_bytes = 255
|
179
|
+
logger.debug "Socket write #{request}" unless logger.nil?
|
180
|
+
response = get_remote_meter_data(request, read_bytes)
|
181
|
+
raise EkmError if response.nil?
|
182
|
+
|
183
|
+
# Split the response string into an array and prepare a hash to store the values
|
184
|
+
a = response.split('')
|
185
|
+
d = {}
|
186
|
+
|
187
|
+
# Return (255 Bytes total) :
|
188
|
+
a.shift(1) # 02
|
189
|
+
d[:meter_type] = a.shift(2) # 2 Byte Meter Type
|
190
|
+
d[:meter_firmware] = a.shift(1) # 1 Byte Meter Firmware
|
191
|
+
d[:address] = a.shift(12) # 12 Bytes Address
|
192
|
+
d[:total_active_kwh] = a.shift(8) # 8 Bytes total Active kWh
|
193
|
+
d[:total_kvarh] = a.shift(8) # 8 Bytes Total kVARh
|
194
|
+
d[:total_rev_kwh] = a.shift(8) # 8 Bytes Total Rev.kWh
|
195
|
+
d[:three_phase_kwh] = a.shift(24) # 24 Bytes 3 phase kWh
|
196
|
+
d[:three_phase_rev_kwh] = a.shift(24) # 24 Bytes 3 phase Rev.kWh
|
197
|
+
d[:resettable_kwh] = a.shift(8) # 8 Bytes Resettable kWh
|
198
|
+
d[:resettable_reverse_kwh] = a.shift(8) # 8 bytes Resettable Reverse kWh
|
199
|
+
d[:volts_l1] = a.shift(4) # 4 Bytes Volts L1
|
200
|
+
d[:volts_l2] = a.shift(4) # 4 Bytes Volts L2
|
201
|
+
d[:volts_l3] = a.shift(4) # 4 Bytes Volts L3
|
202
|
+
d[:amps_l1] = a.shift(5) # 5 Bytes Amps L1
|
203
|
+
d[:amps_l2] = a.shift(5) # 5 Bytes Amps L2
|
204
|
+
d[:amps_l3] = a.shift(5) # 5 Bytes Amps L3
|
205
|
+
d[:watts_l1] = a.shift(7) # 7 Bytes Watts L1
|
206
|
+
d[:watts_l2] = a.shift(7) # 7 Bytes Watts L2
|
207
|
+
d[:watts_l3] = a.shift(7) # 7 Bytes Watts L3
|
208
|
+
d[:watts_total] = a.shift(7) # 7 Bytes Watts Total
|
209
|
+
d[:cosϴ_l1] = a.shift(4) # 4 Bytes Cosϴ L1
|
210
|
+
d[:cosϴ_l2] = a.shift(4) # 4 Bytes Cosϴ L2
|
211
|
+
d[:cosϴ_l3] = a.shift(4) # 4 Bytes Cosϴ L3
|
212
|
+
d[:var_l1] = a.shift(7) # 7 Bytes VAR L1
|
213
|
+
d[:var_l2] = a.shift(7) # 7 Bytes VAR L2
|
214
|
+
d[:var_l3] = a.shift(7) # 7 Bytes VAR L3
|
215
|
+
d[:var_total] = a.shift(7) # 7 Bytes VAR Total
|
216
|
+
d[:freq] = a.shift(4) # 4 Bytes Freq
|
217
|
+
d[:pulse_count_1] = a.shift(8) # 8 Bytes Pulse Count 1
|
218
|
+
d[:pulse_count_2] = a.shift(8) # 8 Bytes Pulse Count 2
|
219
|
+
d[:pulse_count_3] = a.shift(8) # 8 Bytes Pulse Count 3
|
220
|
+
d[:pulse_input_hilo] = a.shift(1) # 1 Byte Pulse Input Hi/Lo
|
221
|
+
d[:direction_of_current] = a.shift(1) # 1 Bytes direction of current
|
222
|
+
d[:outputs_onoff] = a.shift(1) # 1 Byte Outputs On/Off
|
223
|
+
d[:kwh_data_decimal_places] = a.shift(1) # 1 Byte kWh Data Decimal Places
|
224
|
+
a.shift(2) # 2 Bytes Reserved
|
225
|
+
d[:current_time] = a.shift(14) # 14 Bytes Current Time
|
226
|
+
a.shift(6) # 30 30 21 0D 0A 03
|
227
|
+
#d[] = a.shift(2) # 2 Bytes CRC16
|
228
|
+
|
229
|
+
# Smash arrays into strungs
|
230
|
+
d.each {|k,v| d[k] = v.join('')}
|
231
|
+
|
232
|
+
# Merge to values and reset time
|
233
|
+
@values.merge!(d)
|
234
|
+
@last_update = Time.now
|
235
|
+
|
236
|
+
# Calculate totals based on wiring configuration
|
237
|
+
@values[:measurement_timestamp] = measurement_timestamp
|
238
|
+
@values[:volts] = calculate_measurement(d[:volts_l1], d[:volts_l2], d[:volts_l3])
|
239
|
+
@values[:amps] = calculate_measurement(d[:amps_l1], d[:amps_l2], d[:amps_l3])
|
240
|
+
@values[:watts] = calculate_measurement(d[:watts_l1], d[:watts_l2], d[:watts_l3])
|
241
|
+
|
242
|
+
# Return the hash as an open struct
|
243
|
+
return d
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
# Request B:
|
249
|
+
def request_b
|
250
|
+
|
251
|
+
# 2F 3F 12 Bytes Address 30 31 21 0D 0A
|
252
|
+
# /?00000000012301! then a CRLF
|
253
|
+
request = "/?" + meter_number + "01!\r\n"
|
254
|
+
read_bytes = 255
|
255
|
+
logger.debug "Socket write #{request}" unless logger.nil?
|
256
|
+
response = get_remote_meter_data(request, read_bytes)
|
257
|
+
raise EkmError if response.nil?
|
258
|
+
|
259
|
+
# Split the response string into an array and prepare a hash to store the values
|
260
|
+
a = s.split('')
|
261
|
+
d = {}
|
262
|
+
|
263
|
+
# Return (255 Bytes total) :
|
264
|
+
a.shift(1) # 02
|
265
|
+
d[:meter_type] = a.shift(2) # 2 Byte Meter Type
|
266
|
+
d[:meter_firmware] = a.shift(1) # 1 Byte Meter Firmware
|
267
|
+
d[:address] = a.shift(12) # 12 Bytes Address
|
268
|
+
# Diff from request A start
|
269
|
+
d[:t1_t2_t3_t4_kwh] = a.shift(32) # 32 Bytes T1, T2, T3, T4 kwh
|
270
|
+
d[:t1_t2_t3_t4_rev_kwh] = a.shift(32) # 32 Bytes T1, T2, T3, T4 Rev kWh
|
271
|
+
# Diff from request A end
|
272
|
+
d[:volts_l1] = a.shift(4) # 4 Bytes Volts L1
|
273
|
+
d[:volts_l2] = a.shift(4) # 4 Bytes Volts L2
|
274
|
+
d[:volts_l3] = a.shift(4) # 4 Bytes Volts L3
|
275
|
+
d[:amps_l1] = a.shift(5) # 5 Bytes Amps L1
|
276
|
+
d[:amps_l2] = a.shift(5) # 5 Bytes Amps L2
|
277
|
+
d[:amps_l3] = a.shift(5) # 5 Bytes Amps L3
|
278
|
+
d[:watts_l1] = a.shift(7) # 7 Bytes Watts L1
|
279
|
+
d[:watts_l2] = a.shift(7) # 7 Bytes Watts L2
|
280
|
+
d[:watts_l3] = a.shift(7) # 7 Bytes Watts L3
|
281
|
+
d[:watts_total] = a.shift(7) # 7 Bytes Watts Total
|
282
|
+
d[:cosϴ_l1] = a.shift(4) # 4 Bytes Cosϴ L1
|
283
|
+
d[:cosϴ_l2] = a.shift(4) # 4 Bytes Cosϴ L2
|
284
|
+
d[:cosϴ_l3] = a.shift(4) # 4 Bytes Cosϴ L3
|
285
|
+
# Diff from request A start
|
286
|
+
d[:maximum_demand] = a.shift(8) # 8 Bytes Maximum Demand
|
287
|
+
d[:maximum_demand_time] = a.shift(1) # 1 Byte Maximum Demand Time
|
288
|
+
d[:pulse_ratio_1] = a.shift(4) # 4 Bytes Pulse Ratio 1
|
289
|
+
d[:pulse_ratio_2] = a.shift(4) # 4 Bytes Pulse Ratio 2
|
290
|
+
d[:pulse_ratio_3] = a.shift(4) # 4 Bytes Pulse Ratio 3
|
291
|
+
d[:ct_ratio] = a.shift(4) # 4 Bytes CT Ratio
|
292
|
+
d[:auto_reset_md] = a.shift(1) # 1 Bytes Auto Reset MD
|
293
|
+
d[:settable_imp_per_kWh_constant] = a.shift(4) # 4 Bytes Settable Imp/kWh Constant
|
294
|
+
# Diff from request A end
|
295
|
+
a.shift(56) # 56 Bytes Reserved
|
296
|
+
d[:current_time] = a.shift(14) # 14 Bytes Current Time
|
297
|
+
a.shift(6) # 30 30 21 0D 0A 03
|
298
|
+
d[] = a.shift(2) # 2 Bytes CRC16
|
299
|
+
|
300
|
+
# Smash arrays into strungs
|
301
|
+
d.each {|k,v| d[k] = v.join('')}
|
302
|
+
|
303
|
+
# Merge to values and reset time
|
304
|
+
@values.merge!(d)
|
305
|
+
@last_update = Time.now
|
306
|
+
|
307
|
+
# Calculate totals based on wiring configuration
|
308
|
+
@values[:measurement_timestamp] = measurement_timestamp
|
309
|
+
@values[:volts] = calculate_measurement(d[:volts_l1], d[:volts_l2], d[:volts_l3])
|
310
|
+
@values[:amps] = calculate_measurement(d[:amps_l1], d[:amps_l2], d[:amps_l3])
|
311
|
+
@values[:watts] = calculate_measurement(d[:watts_l1], d[:watts_l2], d[:watts_l3])
|
312
|
+
|
313
|
+
# Return the hash as an open struct
|
314
|
+
return d
|
315
|
+
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
|
320
|
+
# Gets remote EKM meter data using iSerial defaults
|
321
|
+
# meter_number is the meters serial number. leading 0s not required.
|
322
|
+
# remote_address is the IP address of the ethernet-RS485 converter
|
323
|
+
# remote_port is the TCP port number the converter is listening to (50000 in my case)
|
324
|
+
# We do not check the checksum - I'm lazy. Probably should. Running for 8 months, querying
|
325
|
+
# once per minute..I've encountered one or two bad results from the meter.
|
326
|
+
def get_remote_meter_data(request, read_bytes)
|
327
|
+
|
328
|
+
logger.debug "get_remote_meter_data #{request} from meter# #{meter_number}" unless logger.nil?
|
329
|
+
|
330
|
+
# connect to the meter and check to make sure we connected
|
331
|
+
begin
|
332
|
+
|
333
|
+
socket = TCPSocket.new(remote_address, remote_port)
|
334
|
+
logger.debug "Socket open" unless logger.nil?
|
335
|
+
|
336
|
+
# Send request to the meter
|
337
|
+
logger.debug "Request: #{request}" unless logger.nil?
|
338
|
+
socket.write(request)
|
339
|
+
|
340
|
+
# Receive a response of 255 bytes
|
341
|
+
response = socket.read(read_bytes)
|
342
|
+
logger.debug "Socket response #{response.length}" unless logger.nil?
|
343
|
+
logger.debug response unless logger.nil?
|
344
|
+
|
345
|
+
rescue Exception => ex
|
346
|
+
logger.error "Exception\n#{ex.message}\n#{ex.backtrace.join("\n")}" unless logger.nil?
|
347
|
+
ensure
|
348
|
+
# EKM Meter software sends this just before closing the connection, so we will too
|
349
|
+
socket.write "\x0a\x03\x32\x3d"
|
350
|
+
socket.close
|
351
|
+
logger.debug "Socket closed" unless logger.nil?
|
352
|
+
end
|
353
|
+
|
354
|
+
return response
|
355
|
+
|
356
|
+
end
|
357
|
+
|
358
|
+
end
|
359
|
+
end
|
data/spec/meter_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EkmOmnimeter::Meter do
|
4
|
+
|
5
|
+
describe "#initialize" do
|
6
|
+
it "should ..." do
|
7
|
+
|
8
|
+
#:meter_number
|
9
|
+
#:remote_address
|
10
|
+
#:remote_port
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#read" do
|
16
|
+
it "should ..." do
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#meter_timestamp" do
|
21
|
+
it "should ..." do
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'rspec'
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
# figure out where we are being loaded from
|
9
|
+
if $LOADED_FEATURES.grep(/spec\/spec_helper\.rb/).any?
|
10
|
+
begin
|
11
|
+
raise "foo"
|
12
|
+
rescue => e
|
13
|
+
puts <<-MSG
|
14
|
+
===================================================
|
15
|
+
It looks like spec_helper.rb has been loaded
|
16
|
+
multiple times. Normalize the require to:
|
17
|
+
|
18
|
+
require "spec/spec_helper"
|
19
|
+
|
20
|
+
Things like File.join and File.expand_path will
|
21
|
+
cause it to be loaded multiple times.
|
22
|
+
|
23
|
+
Loaded this time from:
|
24
|
+
|
25
|
+
#{e.backtrace.join("\n ")}
|
26
|
+
===================================================
|
27
|
+
MSG
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
$:.push File.expand_path("../lib", __FILE__)
|
32
|
+
require 'ekm-omnimeter'
|
33
|
+
|
34
|
+
RSpec.configure do |config|
|
35
|
+
config.color_enabled = true
|
36
|
+
config.formatter = 'documentation'
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ekm-omnimeter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordan Duggan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: log4r
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: xively-rb-connector
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.1'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bigdecimal
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.2'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.2'
|
97
|
+
description: Ruby interface to the EKM Omnimeter Pulse
|
98
|
+
email:
|
99
|
+
- Jordan.Duggan@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- Gemfile
|
106
|
+
- Gemfile.lock
|
107
|
+
- LICENSE
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- ekm-omnimeter.gemspec
|
111
|
+
- lib/ekm-omnimeter.rb
|
112
|
+
- lib/ekm-omnimeter/logging.rb
|
113
|
+
- lib/ekm-omnimeter/meter.rb
|
114
|
+
- lib/ekm-omnimeter/version.rb
|
115
|
+
- spec/meter_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
homepage: https://github.com/jwtd/ekm-omnimeter
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.2.2
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: This ruby gem provides an interface to EKM Omnimeter Pulse meters that are
|
141
|
+
connected to a EKM iSerial TCP/IP Ethernet to RS-485 Serial Converter. Omnimeter
|
142
|
+
Pulse meters contain a revenue-grade universal kWh power meter, three pulse counting
|
143
|
+
inputs for water and/or gas metering, and 2 controllable 50 mA at 12 volts relay
|
144
|
+
outputs.
|
145
|
+
test_files:
|
146
|
+
- spec/meter_spec.rb
|
147
|
+
- spec/spec_helper.rb
|