modbus 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.
- checksums.yaml +7 -0
- data/README.md +62 -0
- data/lib/modbus.rb +191 -0
- data/lib/modbus/adu.rb +48 -0
- data/lib/modbus/exception.rb +24 -0
- data/lib/modbus/request.rb +34 -0
- data/lib/modbus/response.rb +22 -0
- data/lib/modbus/tcp_header.rb +18 -0
- data/modbus.gemspec +27 -0
- data/spec/modbus_spec.rb +73 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5559848be6158b9fdf9c0d2a8d030b08769f181510cfba3a59f90a7a8af9e723
|
4
|
+
data.tar.gz: aade6afbf693c033ccb3bd4b021592d0dc09ceff7247ce4a9f66de3491e0bba2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5b2ff3ee1a9dfcc6ce804879ec11158db23c1581ce4e9134d439fbc1035e7d8b150cd65a291d91dac3a6a2e150459768dcf83a10967f6469eb0c07a3fd55c11a
|
7
|
+
data.tar.gz: f2409419ec8923a69dccc82e6d340138e320126befa6f7f9add418419a24aa376ce1147f0b33d4f8f3e7451e905b9ea226b1a21f655628b1c0d8703f97510d5b
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Ruby Modbus
|
2
|
+
|
3
|
+
Constructs [modbus standard](http://www.modbus.org/specs.php) datagrams that make it easy to communicate with devices on modbus networks.
|
4
|
+
It does not implement the transport layer so you can use it with native ruby, eventmachine, celluloid or the like.
|
5
|
+
|
6
|
+
[](https://travis-ci.org/acaprojects/ruby-modbus)
|
7
|
+
|
8
|
+
You'll need a gateway that supports TCP/IP.
|
9
|
+
|
10
|
+
|
11
|
+
## Install the gem
|
12
|
+
|
13
|
+
Install it with [RubyGems](https://rubygems.org/)
|
14
|
+
|
15
|
+
gem install modbus
|
16
|
+
|
17
|
+
or add this to your Gemfile if you use [Bundler](http://gembundler.com/):
|
18
|
+
|
19
|
+
gem 'modbus'
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'modbus'
|
27
|
+
|
28
|
+
modbus = Modbus.new
|
29
|
+
|
30
|
+
# Reading input obtained from the network
|
31
|
+
# The class instance performs buffering and yields complete ADUs
|
32
|
+
modbus.read(byte_string) do |adu|
|
33
|
+
adu.header.transaction_identifier # => 32
|
34
|
+
|
35
|
+
# Response PDU returned
|
36
|
+
if adu.exception?
|
37
|
+
puts adu.pdu.exception_code
|
38
|
+
else
|
39
|
+
case adu.function_code
|
40
|
+
when 0x01
|
41
|
+
puts adu.pdu.read.data.bytes
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# You can generate requests like so (writing multipe coils starting from 123)
|
48
|
+
request = modbus.write_coils(123, true, true, false)
|
49
|
+
byte_string = request.to_binary_s
|
50
|
+
|
51
|
+
# Read 4 coils starting from address 123
|
52
|
+
request = modbus.read_coils(123, 4)
|
53
|
+
byte_string = request.to_binary_s
|
54
|
+
|
55
|
+
# Send byte_string to the modbus gateway to execute the request
|
56
|
+
|
57
|
+
```
|
58
|
+
|
59
|
+
|
60
|
+
## License and copyright
|
61
|
+
|
62
|
+
MIT
|
data/lib/modbus.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bindata'
|
5
|
+
|
6
|
+
require 'modbus/tcp_header'
|
7
|
+
require 'modbus/request'
|
8
|
+
require 'modbus/response'
|
9
|
+
require 'modbus/exception'
|
10
|
+
require 'modbus/adu'
|
11
|
+
|
12
|
+
class Modbus
|
13
|
+
CODES = {
|
14
|
+
read_coils: 0x01,
|
15
|
+
read_inputs: 0x02,
|
16
|
+
read_holding_registers: 0x03,
|
17
|
+
read_input_registers: 0x04,
|
18
|
+
write_coil: 0x05,
|
19
|
+
write_register: 0x06,
|
20
|
+
write_multiple_coils: 0x0F,
|
21
|
+
write_multiple_registers: 0x10,
|
22
|
+
|
23
|
+
# Serial only
|
24
|
+
read_exception_status: 0x07,
|
25
|
+
diagnostics: 0x08,
|
26
|
+
get_event_counter: 0x0B,
|
27
|
+
get_event_log: 0x0C,
|
28
|
+
get_server_id: 0x11,
|
29
|
+
|
30
|
+
# unsupported
|
31
|
+
read_file_record: 0x14,
|
32
|
+
write_file_record: 0x15,
|
33
|
+
mask_write_register: 0x16,
|
34
|
+
read_write_registers: 0x17,
|
35
|
+
read_fifo_queue: 0x17,
|
36
|
+
encapsulated_interface_transport: 0x2B,
|
37
|
+
canopen_general_request: 0x0D,
|
38
|
+
read_device_identification: 0x0E
|
39
|
+
}
|
40
|
+
CODES.merge!(CODES.invert)
|
41
|
+
CODES.freeze
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@transaction = 0
|
45
|
+
end
|
46
|
+
|
47
|
+
# Decodes an ADU from wire format and sets the attributes of this object.
|
48
|
+
#
|
49
|
+
# @param data [String] The bytes to decode.
|
50
|
+
def read(data)
|
51
|
+
@buffer ||= String.new
|
52
|
+
@buffer << data
|
53
|
+
|
54
|
+
error = nil
|
55
|
+
|
56
|
+
loop do
|
57
|
+
# not enough data in buffer to know the length
|
58
|
+
break if @buffer.bytesize < 6
|
59
|
+
|
60
|
+
header = TCPHeader.new
|
61
|
+
header.read(@buffer[0..5])
|
62
|
+
|
63
|
+
# the headers unit identifier is included in the length
|
64
|
+
total_length = header.request_length + 6
|
65
|
+
|
66
|
+
# Extract just the request from the buffer
|
67
|
+
break if @buffer.bytesize < total_length
|
68
|
+
request = @buffer.slice!(0...total_length)
|
69
|
+
function_code = request.getbyte(7)
|
70
|
+
|
71
|
+
# Yield the complete responses
|
72
|
+
begin
|
73
|
+
if function_code <= 0x80
|
74
|
+
response = ResponsePDU.new
|
75
|
+
response.read(request[6..-1])
|
76
|
+
else # Error response
|
77
|
+
response = ExceptionPDU.new
|
78
|
+
response.read(request[6..-1])
|
79
|
+
function_code = function_code - 0x80
|
80
|
+
end
|
81
|
+
|
82
|
+
yield ADU.new header, function_code, response
|
83
|
+
rescue => e
|
84
|
+
error = e
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
raise error if error
|
89
|
+
end
|
90
|
+
|
91
|
+
[:read_coils, :read_inputs, :read_holding_registers, :read_input_registers].each do |function|
|
92
|
+
define_method function do |address, count = 1|
|
93
|
+
adu = request_adu function
|
94
|
+
request = adu.pdu
|
95
|
+
request.get.address = address.to_i
|
96
|
+
request.get.quantity = count.to_i
|
97
|
+
adu
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def write_coils(address, *values)
|
102
|
+
values = values.flatten
|
103
|
+
|
104
|
+
if values.length > 1
|
105
|
+
write_multiple_coils(address, *values)
|
106
|
+
else
|
107
|
+
adu = request_adu :write_coil
|
108
|
+
request = adu.pdu
|
109
|
+
request.put.address = address.to_i
|
110
|
+
request.put.data = values.first ? 0xFF00 : 0x0
|
111
|
+
adu
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def write_registers(address, *values)
|
116
|
+
values = values.flatten.map! { |value| value.to_i }
|
117
|
+
|
118
|
+
if values.length > 1
|
119
|
+
adu = request_adu :write_multiple_registers
|
120
|
+
request = adu.pdu
|
121
|
+
request.put_multiple.address = address.to_i
|
122
|
+
request.put_multiple.quantity = values.length
|
123
|
+
request.put_multiple.data = values.pack('n*')
|
124
|
+
adu
|
125
|
+
else
|
126
|
+
adu = request_adu :write_register
|
127
|
+
request = adu.pdu
|
128
|
+
request.put.address = address.to_i
|
129
|
+
request.put.data = values.first
|
130
|
+
adu
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
def write_multiple_coils(address, *values)
|
137
|
+
size = values.length
|
138
|
+
|
139
|
+
bytes = []
|
140
|
+
byte = 0
|
141
|
+
bit = 0
|
142
|
+
loop do
|
143
|
+
value = values.shift
|
144
|
+
if value
|
145
|
+
byte = byte | (1 << bit)
|
146
|
+
end
|
147
|
+
break if values.empty?
|
148
|
+
bit += 1
|
149
|
+
if bit >= 8
|
150
|
+
bytes << byte
|
151
|
+
byte = 0
|
152
|
+
bit = 0
|
153
|
+
end
|
154
|
+
end
|
155
|
+
bytes << byte
|
156
|
+
|
157
|
+
adu = request_adu :write_multiple_coils
|
158
|
+
request = adu.pdu
|
159
|
+
request.put_multiple.address = address.to_i
|
160
|
+
request.put_multiple.quantity = size
|
161
|
+
request.put_multiple.data = bytes.pack('C*')
|
162
|
+
adu
|
163
|
+
end
|
164
|
+
|
165
|
+
def next_id
|
166
|
+
id = @transaction
|
167
|
+
@transaction += 1
|
168
|
+
@transaction = 0 if @transaction > 0xFFFF
|
169
|
+
id
|
170
|
+
end
|
171
|
+
|
172
|
+
def new_header
|
173
|
+
header = TCPHeader.new
|
174
|
+
header.transaction_identifier = next_id
|
175
|
+
header.protocol_identifier = 0
|
176
|
+
header
|
177
|
+
end
|
178
|
+
|
179
|
+
def request_pdu(code)
|
180
|
+
request = RequestPDU.new
|
181
|
+
# This seems to be the default
|
182
|
+
request.unit_identifier = 0xFF
|
183
|
+
request.function_code = code
|
184
|
+
request
|
185
|
+
end
|
186
|
+
|
187
|
+
def request_adu(function)
|
188
|
+
code = CODES[function]
|
189
|
+
ADU.new(new_header, code, request_pdu(code))
|
190
|
+
end
|
191
|
+
end
|
data/lib/modbus/adu.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Modbus
|
5
|
+
# TCP ADU are sent via TCP to registered port 502
|
6
|
+
Modbus::ADU = Struct.new :header, :function_code, :pdu do
|
7
|
+
def exception?
|
8
|
+
pdu.is_a?(ExceptionPDU)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_binary_s
|
12
|
+
data = pdu.to_binary_s
|
13
|
+
header.request_length = data.bytesize
|
14
|
+
"#{header.to_binary_s}#{data}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def function_name
|
18
|
+
CODES[function_code]
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
return EXCEPTIONS[pdu.exception_code] || "unknown error 0x#{pdu.exception_code.to_s(16)}" if exception?
|
23
|
+
return nil unless pdu.is_a?(ResponsePDU) && READ_CODES.include?(function_code)
|
24
|
+
|
25
|
+
bytes = pdu.get.data_length
|
26
|
+
bin_str = pdu.get.data
|
27
|
+
|
28
|
+
case function_name
|
29
|
+
when :read_coils, :read_inputs
|
30
|
+
values = []
|
31
|
+
|
32
|
+
# extract the bits and return an array of true / false values
|
33
|
+
bin_str.each_byte do |byte|
|
34
|
+
bit = 0
|
35
|
+
loop do
|
36
|
+
values << ((byte & (1 << bit)) > 0)
|
37
|
+
bit += 1
|
38
|
+
break if bit >= 8
|
39
|
+
end
|
40
|
+
end
|
41
|
+
values
|
42
|
+
when :read_holding_registers, :read_input_registers
|
43
|
+
# these are all 16bit integers
|
44
|
+
bin_str.unpack('n*')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Modbus
|
5
|
+
EXCEPTIONS = {
|
6
|
+
0x01 => 'illegal function',
|
7
|
+
0x02 => 'illegal data address',
|
8
|
+
0x03 => 'illegal data value',
|
9
|
+
0x04 => 'server device failure',
|
10
|
+
0x05 => 'acknowledge', # processing will take some time, no need to retry
|
11
|
+
0x06 => 'server device busy',
|
12
|
+
0x08 => 'memory parity error',
|
13
|
+
0x0A => 'gateway path unavailable',
|
14
|
+
0x0B => 'gateway device failed to respond'
|
15
|
+
}
|
16
|
+
|
17
|
+
class ExceptionPDU < BinData::Record
|
18
|
+
endian :big
|
19
|
+
|
20
|
+
uint8 :unit_identifier
|
21
|
+
uint8 :function_code
|
22
|
+
uint8 :exception_code
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Modbus
|
5
|
+
READ_CODES = [0x01, 0x02, 0x03, 0x04].freeze
|
6
|
+
WRITE_CODES = [0x05, 0x06].freeze
|
7
|
+
MULTIPLE_CODES = [0x0F, 0x10].freeze
|
8
|
+
|
9
|
+
class RequestPDU < BinData::Record
|
10
|
+
endian :big
|
11
|
+
|
12
|
+
# This field is used for intra-system routing purpose (default to 0xFF)
|
13
|
+
uint8 :unit_identifier
|
14
|
+
uint8 :function_code
|
15
|
+
|
16
|
+
struct :get, onlyif: -> { READ_CODES.include? function_code } do
|
17
|
+
uint16 :address
|
18
|
+
uint16 :quantity
|
19
|
+
end
|
20
|
+
|
21
|
+
struct :put, onlyif: -> { WRITE_CODES.include? function_code } do
|
22
|
+
uint16 :address
|
23
|
+
uint16 :data
|
24
|
+
end
|
25
|
+
|
26
|
+
struct :put_multiple, onlyif: -> { MULTIPLE_CODES.include? function_code } do
|
27
|
+
uint16 :address
|
28
|
+
uint16 :quantity
|
29
|
+
|
30
|
+
uint8 :data_length, value: -> { put_multiple.data.bytesize }
|
31
|
+
string :data, read_length: -> { put_multiple.data_length }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Modbus
|
5
|
+
class ResponsePDU < BinData::Record
|
6
|
+
endian :big
|
7
|
+
|
8
|
+
# This field is used for intra-system routing purpose (default to 0xFF)
|
9
|
+
uint8 :unit_identifier
|
10
|
+
uint8 :function_code
|
11
|
+
|
12
|
+
struct :get, onlyif: -> { READ_CODES.include? function_code } do
|
13
|
+
uint8 :data_length, value: -> { get.data.bytesize }
|
14
|
+
string :data, read_length: -> { get.data_length }
|
15
|
+
end
|
16
|
+
|
17
|
+
struct :put, onlyif: -> { WRITE_CODES.include?(function_code) || MULTIPLE_CODES.include?(function_code) } do
|
18
|
+
uint16 :address
|
19
|
+
uint16 :data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Modbus
|
5
|
+
# TCP ADU are sent via TCP to registered port 502
|
6
|
+
class TCPHeader < BinData::Record
|
7
|
+
endian :big
|
8
|
+
|
9
|
+
# used for transaction pairing
|
10
|
+
uint16 :transaction_identifier
|
11
|
+
|
12
|
+
# The MODBUS protocol is identified by the value 0.
|
13
|
+
uint16 :protocol_identifier
|
14
|
+
|
15
|
+
# byte count of the following fields, including the Unit Identifier and data fields.
|
16
|
+
uint16 :request_length
|
17
|
+
end
|
18
|
+
end
|
data/modbus.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "modbus"
|
5
|
+
s.version = '1.0.0'
|
6
|
+
s.authors = ["Stephen von Takach"]
|
7
|
+
s.email = ["steve@aca.im"]
|
8
|
+
s.licenses = ["MIT"]
|
9
|
+
s.homepage = "https://github.com/acaprojects/ruby-modbus"
|
10
|
+
s.summary = "Modbus protocol on Ruby"
|
11
|
+
s.description = <<-EOF
|
12
|
+
Constructs Modbus standard datagrams that make it easy to communicate with devices on Modbus networks
|
13
|
+
EOF
|
14
|
+
|
15
|
+
|
16
|
+
s.add_dependency 'bindata', '~> 2.3'
|
17
|
+
|
18
|
+
s.add_development_dependency 'rspec', '~> 3.5'
|
19
|
+
s.add_development_dependency 'rake', '~> 12'
|
20
|
+
|
21
|
+
|
22
|
+
s.files = Dir["{lib}/**/*"] + %w(modbus.gemspec README.md)
|
23
|
+
s.test_files = Dir["spec/**/*"]
|
24
|
+
s.extra_rdoc_files = ["README.md"]
|
25
|
+
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
data/spec/modbus_spec.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: ASCII-8BIT
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'modbus'
|
5
|
+
|
6
|
+
# Example captures
|
7
|
+
# http://www.pcapr.net/browse?q=modbus
|
8
|
+
|
9
|
+
describe 'modbus protocol helper' do
|
10
|
+
before :each do
|
11
|
+
@modbus = Modbus.new
|
12
|
+
@response = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should parse and generate the same string" do
|
16
|
+
data = "\x0\x0\x0\x0\x0\x4\x1\x1\x1\x1"
|
17
|
+
@modbus.read(data) { |adu| @response = adu }
|
18
|
+
expect(@response.to_binary_s).to eq(data)
|
19
|
+
@response = nil
|
20
|
+
|
21
|
+
data = "\x0\x0\x0\x0\x0\x4\x1\x2\x1\x1"
|
22
|
+
@modbus.read(data) { |adu| @response = adu }
|
23
|
+
expect(@response.to_binary_s).to eq(data)
|
24
|
+
@response = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should generate a read coil request" do
|
28
|
+
data = @modbus.read_coils(0).to_binary_s
|
29
|
+
expect(data).to eq("\x0\x0\x0\x0\x0\x6\xFF\x1\x0\x0\x0\x1")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should generate a read inputs request" do
|
33
|
+
data = @modbus.read_inputs(0).to_binary_s
|
34
|
+
expect(data).to eq("\x0\x0\x0\x0\x0\x6\xFF\x2\x0\x0\x0\x1")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should generate a write coils request" do
|
38
|
+
data = @modbus.write_coils(4, true).to_binary_s
|
39
|
+
expect(data).to eq("\x00\x00\x00\x00\x00\x06\xFF\x05\x00\x04\xFF\x00")
|
40
|
+
|
41
|
+
data = @modbus.write_coils(3, false).to_binary_s
|
42
|
+
expect(data).to eq("\x00\x01\x00\x00\x00\x06\xFF\x05\x00\x03\x00\x00")
|
43
|
+
|
44
|
+
data = @modbus.write_coils(4, true, true).to_binary_s
|
45
|
+
expect(data).to eq("\x00\x02\x00\x00\x00\x08\xFF\x0F\x00\x04\x00\x02\x1\x3")
|
46
|
+
|
47
|
+
data = @modbus.write_coils(4, false, true).to_binary_s
|
48
|
+
expect(data).to eq("\x00\x03\x00\x00\x00\x08\xFF\x0F\x00\x04\x00\x02\x1\x2")
|
49
|
+
|
50
|
+
data = @modbus.write_coils(4, true, true, true, true, true, true, true, true, false, true).to_binary_s
|
51
|
+
expect(data).to eq("\x00\x04\x00\x00\x00\x09\xFF\x0F\x00\x04\x00\x0A\x2\xFF\x2")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should generate a write registers request" do
|
55
|
+
data = @modbus.write_registers(5, [1, 2, 3, 4]).to_binary_s
|
56
|
+
expect(data).to eq("\x00\x00\x00\x00\x00\x0F\xFF\x10\x00\x05\x00\x04\x8\x00\x01\x00\x02\x00\x03\x00\x04")
|
57
|
+
|
58
|
+
data = @modbus.write_registers(5, 4).to_binary_s
|
59
|
+
expect(data).to eq("\x00\x01\x00\x00\x00\x06\xFF\x06\x00\x05\x00\x04")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return the response values" do
|
63
|
+
data = "\x00\x00\x00\x00\x00\x04\xFF\x01\x1\x3"
|
64
|
+
@modbus.read(data) { |adu| @response = adu }
|
65
|
+
expect(@response.value).to eq([true, true, false, false, false, false, false, false])
|
66
|
+
expect(@response.function_name).to eq(:read_coils)
|
67
|
+
|
68
|
+
data = "\x00\x00\x00\x00\x00\x07\xFF\x03\x4\x0\x3\x1\x00"
|
69
|
+
@modbus.read(data) { |adu| @response = adu }
|
70
|
+
expect(@response.value).to eq([3, 256])
|
71
|
+
expect(@response.function_name).to eq(:read_holding_registers)
|
72
|
+
end
|
73
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: modbus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephen von Takach
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bindata
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12'
|
55
|
+
description: " Constructs Modbus standard datagrams that make it easy to communicate
|
56
|
+
with devices on Modbus networks\n"
|
57
|
+
email:
|
58
|
+
- steve@aca.im
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files:
|
62
|
+
- README.md
|
63
|
+
files:
|
64
|
+
- README.md
|
65
|
+
- lib/modbus.rb
|
66
|
+
- lib/modbus/adu.rb
|
67
|
+
- lib/modbus/exception.rb
|
68
|
+
- lib/modbus/request.rb
|
69
|
+
- lib/modbus/response.rb
|
70
|
+
- lib/modbus/tcp_header.rb
|
71
|
+
- modbus.gemspec
|
72
|
+
- spec/modbus_spec.rb
|
73
|
+
homepage: https://github.com/acaprojects/ruby-modbus
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.7.7
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Modbus protocol on Ruby
|
97
|
+
test_files:
|
98
|
+
- spec/modbus_spec.rb
|