rmodbus 1.0.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/NEWS.md +52 -0
- data/README.md +87 -0
- data/Rakefile +31 -0
- data/examples/perfomance_rtu.rb +56 -0
- data/examples/perfomance_tcp.rb +55 -0
- data/examples/use_rtu_via_tcp_modbus.rb +22 -0
- data/examples/use_tcp_modbus.rb +23 -0
- data/lib/rmodbus/client.rb +91 -0
- data/lib/rmodbus/common.rb +50 -0
- data/lib/rmodbus/errors.rb +50 -0
- data/lib/rmodbus/ext.rb +88 -0
- data/lib/rmodbus/proxy.rb +54 -0
- data/lib/rmodbus/rtu.rb +140 -0
- data/lib/rmodbus/rtu_client.rb +41 -0
- data/lib/rmodbus/rtu_server.rb +61 -0
- data/lib/rmodbus/rtu_slave.rb +59 -0
- data/lib/rmodbus/rtu_via_tcp_client.rb +41 -0
- data/lib/rmodbus/rtu_via_tcp_server.rb +50 -0
- data/lib/rmodbus/rtu_via_tcp_slave.rb +58 -0
- data/lib/rmodbus/server.rb +142 -0
- data/lib/rmodbus/slave.rb +268 -0
- data/lib/rmodbus/sp.rb +45 -0
- data/lib/rmodbus/tcp.rb +49 -0
- data/lib/rmodbus/tcp_client.rb +39 -0
- data/lib/rmodbus/tcp_server.rb +61 -0
- data/lib/rmodbus/tcp_slave.rb +64 -0
- data/lib/rmodbus/version.rb +17 -0
- data/lib/rmodbus.rb +35 -0
- data/spec/client_spec.rb +31 -0
- data/spec/exception_spec.rb +116 -0
- data/spec/ext_spec.rb +46 -0
- data/spec/logging_spec.rb +65 -0
- data/spec/proxy_spec.rb +73 -0
- data/spec/read_rtu_response_spec.rb +91 -0
- data/spec/rtu_client_spec.rb +74 -0
- data/spec/rtu_server_spec.rb +29 -0
- data/spec/rtu_via_tcp_client_spec.rb +78 -0
- data/spec/slave_spec.rb +55 -0
- data/spec/tcp_client_spec.rb +81 -0
- data/spec/tcp_server_spec.rb +99 -0
- metadata +165 -0
@@ -0,0 +1,268 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2008-2011 Timin Aleksey
|
4
|
+
# Copyright (C) 2010 Kelley Reynolds
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
|
16
|
+
module ModBus
|
17
|
+
class Slave
|
18
|
+
include Errors
|
19
|
+
include Common
|
20
|
+
# Number of times to retry on read and read timeouts
|
21
|
+
attr_accessor :read_retries, :read_retry_timeout, :uid
|
22
|
+
|
23
|
+
Exceptions = {
|
24
|
+
1 => IllegalFunction.new("The function code received in the query is not an allowable action for the server"),
|
25
|
+
2 => IllegalDataAddress.new("The data address received in the query is not an allowable address for the server"),
|
26
|
+
3 => IllegalDataValue.new("A value contained in the query data field is not an allowable value for server"),
|
27
|
+
4 => SlaveDeviceFailure.new("An unrecoverable error occurred while the server was attempting to perform the requested action"),
|
28
|
+
5 => Acknowledge.new("The server has accepted the request and is processing it, but a long duration of time will be required to do so"),
|
29
|
+
6 => SlaveDeviceBus.new("The server is engaged in processing a long duration program command"),
|
30
|
+
8 => MemoryParityError.new("The extended file area failed to pass a consistency check")
|
31
|
+
}
|
32
|
+
def initialize(uid, io)
|
33
|
+
@uid = uid
|
34
|
+
@read_retries = 10
|
35
|
+
@read_retry_timeout = 1
|
36
|
+
@io = io
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a ModBus::ReadWriteProxy hash interface for coils
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# coils[addr] => [1]
|
43
|
+
# coils[addr1..addr2] => [1, 0, ..]
|
44
|
+
# coils[addr] = 0 => [0]
|
45
|
+
# coils[addr1..addr2] = [1, 0, ..] => [1, 0, ..]
|
46
|
+
#
|
47
|
+
# @return [ReadWriteProxy] proxy object
|
48
|
+
def coils
|
49
|
+
ModBus::ReadWriteProxy.new(self, :coil)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Read coils
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# read_coils(addr, ncoils) => [1, 0, ..]
|
56
|
+
#
|
57
|
+
# @param [Integer] addr address first coil
|
58
|
+
# @param [Integer] ncoils number coils
|
59
|
+
# @return [Array] coils
|
60
|
+
def read_coils(addr, ncoils)
|
61
|
+
query("\x1" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
|
62
|
+
end
|
63
|
+
alias_method :read_coil, :read_coils
|
64
|
+
|
65
|
+
# Write a single coil
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# write_single_coil(1, 0) => self
|
69
|
+
#
|
70
|
+
# @param [Integer] addr address coil
|
71
|
+
# @param [Integer] val value coil (0 or other)
|
72
|
+
# @return self
|
73
|
+
def write_single_coil(addr, val)
|
74
|
+
if val == 0
|
75
|
+
query("\x5" + addr.to_word + 0.to_word)
|
76
|
+
else
|
77
|
+
query("\x5" + addr.to_word + 0xff00.to_word)
|
78
|
+
end
|
79
|
+
self
|
80
|
+
end
|
81
|
+
alias_method :write_coil, :write_single_coil
|
82
|
+
|
83
|
+
# Write multiple coils
|
84
|
+
#
|
85
|
+
# @example
|
86
|
+
# write_multiple_coils(1, [0,1,0,1]) => self
|
87
|
+
#
|
88
|
+
# @param [Integer] addr address first coil
|
89
|
+
# @param [Array] vals written coils
|
90
|
+
def write_multiple_coils(addr, vals)
|
91
|
+
nbyte = ((vals.size-1) >> 3) + 1
|
92
|
+
sum = 0
|
93
|
+
(vals.size - 1).downto(0) do |i|
|
94
|
+
sum = sum << 1
|
95
|
+
sum |= 1 if vals[i] > 0
|
96
|
+
end
|
97
|
+
|
98
|
+
s_val = ""
|
99
|
+
nbyte.times do
|
100
|
+
s_val << (sum & 0xff).chr
|
101
|
+
sum >>= 8
|
102
|
+
end
|
103
|
+
|
104
|
+
query("\xf" + addr.to_word + vals.size.to_word + nbyte.chr + s_val)
|
105
|
+
self
|
106
|
+
end
|
107
|
+
alias_method :write_coils, :write_multiple_coils
|
108
|
+
|
109
|
+
# Returns a ModBus::ReadOnlyProxy hash interface for discrete inputs
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# discrete_inputs[addr] => [1]
|
113
|
+
# discrete_inputs[addr1..addr2] => [1, 0, ..]
|
114
|
+
#
|
115
|
+
# @return [ReadOnlyProxy] proxy object
|
116
|
+
def discrete_inputs
|
117
|
+
ModBus::ReadOnlyProxy.new(self, :discrete_input)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Read discrete inputs
|
121
|
+
#
|
122
|
+
# @example
|
123
|
+
# read_discrete_inputs(addr, ninputs) => [1, 0, ..]
|
124
|
+
#
|
125
|
+
# @param [Integer] addr address first input
|
126
|
+
# @param[Integer] ninputs number inputs
|
127
|
+
# @return [Array] inputs
|
128
|
+
def read_discrete_inputs(addr, ninputs)
|
129
|
+
query("\x2" + addr.to_word + ninputs.to_word).unpack_bits[0..ninputs-1]
|
130
|
+
end
|
131
|
+
alias_method :read_discrete_input, :read_discrete_inputs
|
132
|
+
|
133
|
+
# Returns a read/write ModBus::ReadOnlyProxy hash interface for coils
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
# input_registers[addr] => [1]
|
137
|
+
# input_registers[addr1..addr2] => [1, 0, ..]
|
138
|
+
#
|
139
|
+
# @return [ReadOnlyProxy] proxy object
|
140
|
+
def input_registers
|
141
|
+
ModBus::ReadOnlyProxy.new(self, :input_register)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Read input registers
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# read_input_registers(1, 5) => [1, 0, ..]
|
148
|
+
#
|
149
|
+
# @param [Integer] addr address first registers
|
150
|
+
# @param [Integer] nregs number registers
|
151
|
+
# @return [Array] registers
|
152
|
+
def read_input_registers(addr, nregs)
|
153
|
+
query("\x4" + addr.to_word + nregs.to_word).unpack('n*')
|
154
|
+
end
|
155
|
+
alias_method :read_input_register, :read_input_registers
|
156
|
+
|
157
|
+
# Returns a ModBus::ReadWriteProxy hash interface for holding registers
|
158
|
+
#
|
159
|
+
# @example
|
160
|
+
# holding_registers[addr] => [123]
|
161
|
+
# holding_registers[addr1..addr2] => [123, 234, ..]
|
162
|
+
# holding_registers[addr] = 123 => 123
|
163
|
+
# holding_registers[addr1..addr2] = [234, 345, ..] => [234, 345, ..]
|
164
|
+
#
|
165
|
+
# @return [ReadWriteProxy] proxy object
|
166
|
+
def holding_registers
|
167
|
+
ModBus::ReadWriteProxy.new(self, :holding_register)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Read holding registers
|
171
|
+
#
|
172
|
+
# @example
|
173
|
+
# read_holding_registers(1, 5) => [1, 0, ..]
|
174
|
+
#
|
175
|
+
# @param [Integer] addr address first registers
|
176
|
+
# @param [Integer] nregs number registers
|
177
|
+
# @return [Array] registers
|
178
|
+
def read_holding_registers(addr, nregs)
|
179
|
+
query("\x3" + addr.to_word + nregs.to_word).unpack('n*')
|
180
|
+
end
|
181
|
+
alias_method :read_holding_register, :read_holding_registers
|
182
|
+
|
183
|
+
# Write a single holding register
|
184
|
+
#
|
185
|
+
# @example
|
186
|
+
# write_single_register(1, 0xaa) => self
|
187
|
+
#
|
188
|
+
# @param [Integer] addr address registers
|
189
|
+
# @param [Integer] val written to register
|
190
|
+
# @return self
|
191
|
+
def write_single_register(addr, val)
|
192
|
+
query("\x6" + addr.to_word + val.to_word)
|
193
|
+
self
|
194
|
+
end
|
195
|
+
alias_method :write_holding_register, :write_single_register
|
196
|
+
|
197
|
+
|
198
|
+
# Write multiple holding registers
|
199
|
+
#
|
200
|
+
# @example
|
201
|
+
# write_multiple_registers(1, [0xaa, 0]) => self
|
202
|
+
#
|
203
|
+
# @param [Integer] addr address first registers
|
204
|
+
# @param [Array] val written registers
|
205
|
+
# @return self
|
206
|
+
def write_multiple_registers(addr, vals)
|
207
|
+
s_val = ""
|
208
|
+
vals.each do |reg|
|
209
|
+
s_val << reg.to_word
|
210
|
+
end
|
211
|
+
|
212
|
+
query("\x10" + addr.to_word + vals.size.to_word + (vals.size * 2).chr + s_val)
|
213
|
+
self
|
214
|
+
end
|
215
|
+
alias_method :write_holding_registers, :write_multiple_registers
|
216
|
+
|
217
|
+
# Mask a holding register
|
218
|
+
#
|
219
|
+
# @example
|
220
|
+
# mask_write_register(1, 0xAAAA, 0x00FF) => self
|
221
|
+
# @param [Integer] addr address registers
|
222
|
+
# @param [Integer] and_mask mask for AND operation
|
223
|
+
# @param [Integer] or_mask mask for OR operation
|
224
|
+
def mask_write_register(addr, and_mask, or_mask)
|
225
|
+
query("\x16" + addr.to_word + and_mask.to_word + or_mask.to_word)
|
226
|
+
self
|
227
|
+
end
|
228
|
+
|
229
|
+
# Request pdu to slave device
|
230
|
+
#
|
231
|
+
# @param [String] pdu request to slave
|
232
|
+
# @return [String] received data
|
233
|
+
#
|
234
|
+
# @raise [ModBusTimeout] timed out during read attempt
|
235
|
+
# @raise [ModBusException] unknown error
|
236
|
+
# @raise [IllegalFunction] function code received in the query is not an allowable action for the server
|
237
|
+
# @raise [IllegalDataAddress] data address received in the query is not an allowable address for the server
|
238
|
+
# @raise [IllegalDataValue] value contained in the query data field is not an allowable value for server
|
239
|
+
# @raise [SlaveDeviceFailure] unrecoverable error occurred while the server was attempting to perform the requested action
|
240
|
+
# @raise [Acknowledge] server has accepted the request and is processing it, but a long duration of time will be required to do so
|
241
|
+
# @raise [SlaveDeviceBus] server is engaged in processing a long duration program command
|
242
|
+
# @raise [MemoryParityError] extended file area failed to pass a consistency check
|
243
|
+
def query(pdu)
|
244
|
+
tried = 0
|
245
|
+
begin
|
246
|
+
timeout(@read_retry_timeout, ModBusTimeout) do
|
247
|
+
send_pdu(pdu)
|
248
|
+
pdu = read_pdu
|
249
|
+
end
|
250
|
+
rescue ModBusTimeout => err
|
251
|
+
log "Timeout of read operation: (#{@read_retries - tried})"
|
252
|
+
tried += 1
|
253
|
+
retry unless tried >= @read_retries
|
254
|
+
raise ModBusTimeout.new, "Timed out during read attempt"
|
255
|
+
end
|
256
|
+
|
257
|
+
return nil if pdu.size == 0
|
258
|
+
|
259
|
+
if pdu.getbyte(0) >= 0x80
|
260
|
+
exc_id = pdu.getbyte(1)
|
261
|
+
raise Exceptions[exc_id] unless Exceptions[exc_id].nil?
|
262
|
+
|
263
|
+
raise ModBusException.new, "Unknown error"
|
264
|
+
end
|
265
|
+
pdu[2..-1]
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
data/lib/rmodbus/sp.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Timin Aleksey
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
|
15
|
+
require 'serialport'
|
16
|
+
|
17
|
+
module ModBus
|
18
|
+
module SP
|
19
|
+
attr_reader :port, :baud, :data_bits, :stop_bits, :parity, :read_timeout
|
20
|
+
# Open serial port
|
21
|
+
# @param [String] port name serial ports ("/dev/ttyS0" POSIX, "com1" - Windows)
|
22
|
+
# @param [Integer] baud rate serial port (default 9600)
|
23
|
+
# @param [Hash] opts the options of serial port
|
24
|
+
#
|
25
|
+
# @option opts [Integer] :data_bits from 5 to 8
|
26
|
+
# @option opts [Integer] :stop_bits 1 or 2
|
27
|
+
# @option opts [Integer] :parity NONE, EVEN or ODD
|
28
|
+
# @option opts [Integer] :read_timeout default 100 ms
|
29
|
+
# @return [SerialPort] io serial port
|
30
|
+
def open_serial_port(port, baud, opts = {})
|
31
|
+
@port, @baud = port, baud
|
32
|
+
|
33
|
+
@data_bits, @stop_bits, @parity, @read_timeout = 8, 1, SerialPort::NONE, 100
|
34
|
+
|
35
|
+
@data_bits = opts[:data_bits] unless opts[:data_bits].nil?
|
36
|
+
@stop_bits = opts[:stop_bits] unless opts[:stop_bits].nil?
|
37
|
+
@parity = opts[:parity] unless opts[:parity].nil?
|
38
|
+
@read_timeout = options[:read_timeout] unless opts[:read_timeout].nil?
|
39
|
+
|
40
|
+
io = SerialPort.new(@port, @baud, @data_bits, @stop_bits, @parity)
|
41
|
+
io.read_timeout = @read_timeout
|
42
|
+
io
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/rmodbus/tcp.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Timin Aleksey
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
require 'socket'
|
15
|
+
require 'timeout'
|
16
|
+
|
17
|
+
module ModBus
|
18
|
+
module TCP
|
19
|
+
include Timeout
|
20
|
+
attr_reader :ipaddr, :port
|
21
|
+
|
22
|
+
private
|
23
|
+
# Open TCP socket
|
24
|
+
#
|
25
|
+
# @param [String] ipaddr IP address of remote server
|
26
|
+
# @param [Integer] port connection port
|
27
|
+
# @param [Hash] opts options of connection
|
28
|
+
# @option opts [Float, Integer] :connect_timeout seconds timeout for open socket
|
29
|
+
# @return [TCPSocket] socket
|
30
|
+
#
|
31
|
+
# @raise [ModBusTimeout] timed out attempting to create connection
|
32
|
+
def open_tcp_connection(ipaddr, port, opts = {})
|
33
|
+
@ipaddr, @port = ipaddr, port
|
34
|
+
|
35
|
+
opts[:connect_timeout] ||= 1
|
36
|
+
|
37
|
+
io = nil
|
38
|
+
begin
|
39
|
+
timeout(opts[:connect_timeout], ModBusTimeout) do
|
40
|
+
io = TCPSocket.new(@ipaddr, @port)
|
41
|
+
end
|
42
|
+
rescue ModBusTimeout => err
|
43
|
+
raise ModBusTimeout.new, 'Timed out attempting to create connection'
|
44
|
+
end
|
45
|
+
|
46
|
+
io
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2008-2011 Timin Aleksey
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
module ModBus
|
15
|
+
# TCP client implementation
|
16
|
+
# @example
|
17
|
+
# TCPClient.connect('127.0.0.1', 502) do |cl|
|
18
|
+
# cl.with_slave(uid) do |slave|
|
19
|
+
# slave.holding_registers[0..100]
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @see TCPClient#open_connection
|
24
|
+
# @see Client#initialize
|
25
|
+
class TCPClient < Client
|
26
|
+
include TCP
|
27
|
+
|
28
|
+
protected
|
29
|
+
# Open TCP\IP connection
|
30
|
+
# @see TCP::open_connection
|
31
|
+
def open_connection(ipaddr, port = 502, opts = {})
|
32
|
+
open_tcp_connection(ipaddr, port, opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_slave(uid, io)
|
36
|
+
TCPSlave.new(uid, io)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2008 Timin Aleksey
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
require 'gserver'
|
15
|
+
|
16
|
+
module ModBus
|
17
|
+
# TCP server implementation
|
18
|
+
# @example
|
19
|
+
# srv = TCPServer.new(10002, 1)
|
20
|
+
# srv.coils = [1,0,1,1]
|
21
|
+
# srv.discrete_inputs = [1,1,0,0]
|
22
|
+
# srv.holding_registers = [1,2,3,4]
|
23
|
+
# srv.input_registers = [1,2,3,4]
|
24
|
+
# srv.debug = true
|
25
|
+
# srv.start
|
26
|
+
class TCPServer < GServer
|
27
|
+
include Common
|
28
|
+
include Server
|
29
|
+
|
30
|
+
# Init server
|
31
|
+
# @param [Integer] port listen port
|
32
|
+
# @param [Integer] uid slave device
|
33
|
+
def initialize(port = 502, uid = 1)
|
34
|
+
@uid = uid
|
35
|
+
super(port)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Serve requests
|
39
|
+
# @param [TCPSocket] io socket
|
40
|
+
def serve(io)
|
41
|
+
loop do
|
42
|
+
req = io.read(7)
|
43
|
+
if req[2,2] != "\x00\x00" or req.getbyte(6) != @uid
|
44
|
+
io.close
|
45
|
+
break
|
46
|
+
end
|
47
|
+
|
48
|
+
tr = req[0,2]
|
49
|
+
len = req[4,2].unpack('n')[0]
|
50
|
+
req = io.read(len - 1)
|
51
|
+
log "Server RX (#{req.size} bytes): #{logging_bytes(req)}"
|
52
|
+
|
53
|
+
pdu = exec_req(req, @coils, @discrete_inputs, @holding_registers, @input_registers)
|
54
|
+
|
55
|
+
resp = tr + "\0\0" + (pdu.size + 1).to_word + @uid.chr + pdu
|
56
|
+
log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
|
57
|
+
io.write resp
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Timin Aleksey
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
module ModBus
|
15
|
+
# TCP slave implementation
|
16
|
+
# @example
|
17
|
+
# TCP.connect('127.0.0.1', 10002) do |cl|
|
18
|
+
# cl.with_slave(uid) do |slave|
|
19
|
+
# slave.holding_registers[0..100]
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @see RTUViaTCPClient#open_connection
|
24
|
+
# @see Client#with_slave
|
25
|
+
# @see Slave
|
26
|
+
class TCPSlave < Slave
|
27
|
+
attr_reader :transaction
|
28
|
+
|
29
|
+
# @see Slave::initialize
|
30
|
+
def initialize(uid, io)
|
31
|
+
@transaction = 0
|
32
|
+
super(uid, io)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
# overide method for RTU over TCP implamentaion
|
37
|
+
# @see Slave#query
|
38
|
+
def send_pdu(pdu)
|
39
|
+
@transaction = 0 if @transaction.next > 65535
|
40
|
+
@transaction += 1
|
41
|
+
msg = @transaction.to_word + "\0\0" + (pdu.size + 1).to_word + @uid.chr + pdu
|
42
|
+
@io.write msg
|
43
|
+
|
44
|
+
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
45
|
+
end
|
46
|
+
|
47
|
+
# overide method for RTU over TCP implamentaion
|
48
|
+
# @see Slave#query
|
49
|
+
def read_pdu
|
50
|
+
header = @io.read(7)
|
51
|
+
if header
|
52
|
+
tin = header[0,2].unpack('n')[0]
|
53
|
+
raise Errors::ModBusException.new("Transaction number mismatch") unless tin == @transaction
|
54
|
+
len = header[4,2].unpack('n')[0]
|
55
|
+
msg = @io.read(len-1)
|
56
|
+
|
57
|
+
log "Rx (#{(header + msg).size} bytes): " + logging_bytes(header + msg)
|
58
|
+
msg
|
59
|
+
else
|
60
|
+
raise Errors::ModBusException.new("Server did not respond")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 Timin Aleksey
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
module ModBus
|
15
|
+
# Package version
|
16
|
+
VERSION = '1.0.0'
|
17
|
+
end
|
data/lib/rmodbus.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
|
+
# Copyright (C) 2008 - 2011 Timin Aleksey
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
require 'rmodbus/errors'
|
13
|
+
require 'rmodbus/ext'
|
14
|
+
require 'rmodbus/common'
|
15
|
+
require 'rmodbus/rtu'
|
16
|
+
require 'rmodbus/tcp'
|
17
|
+
require 'rmodbus/slave'
|
18
|
+
require 'rmodbus/client'
|
19
|
+
require 'rmodbus/server'
|
20
|
+
require 'rmodbus/tcp_slave'
|
21
|
+
require 'rmodbus/tcp_client'
|
22
|
+
require 'rmodbus/tcp_server'
|
23
|
+
|
24
|
+
# jruby not support serial RTU protocol yet
|
25
|
+
unless RUBY_PLATFORM == "java"
|
26
|
+
require 'rmodbus/sp'
|
27
|
+
require 'rmodbus/rtu_slave'
|
28
|
+
require 'rmodbus/rtu_client'
|
29
|
+
require 'rmodbus/rtu_server'
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'rmodbus/rtu_via_tcp_slave'
|
33
|
+
require 'rmodbus/rtu_via_tcp_client'
|
34
|
+
require 'rmodbus/rtu_via_tcp_server'
|
35
|
+
require 'rmodbus/proxy'
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
include ModBus
|
3
|
+
|
4
|
+
describe Client do
|
5
|
+
before do
|
6
|
+
@cl = Client.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should give object provider for slave" do
|
10
|
+
slave = @cl.with_slave(1)
|
11
|
+
slave.uid.should eq(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should give object provider for slave in block" do
|
15
|
+
@cl.with_slave(1) do |slave|
|
16
|
+
slave.uid.should eq(1)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should connect with TCP server" do
|
21
|
+
Client.connect do |cl|
|
22
|
+
cl.should be_instance_of(Client)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it ":new alias :connect" do
|
27
|
+
Client.new do |cl|
|
28
|
+
cl.should be_instance_of(Client)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|