modbus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/acaprojects/ruby-modbus.svg?branch=master)](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
|