rmodbus 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +1 -0
- data/ChangeLog +26 -0
- data/README +6 -4
- data/examples/use_rtu_via_tcp_modbus.rb +19 -0
- data/examples/use_tcp_modbus.rb +6 -5
- data/lib/rmodbus.rb +3 -0
- data/lib/rmodbus/client.rb +54 -37
- data/lib/rmodbus/common.rb +23 -0
- data/lib/rmodbus/ext.rb +2 -2
- data/lib/rmodbus/rtu_client.rb +13 -13
- data/lib/rmodbus/rtu_via_tcp_client.rb +105 -0
- data/lib/rmodbus/rtu_via_tcp_server.rb +114 -0
- data/lib/rmodbus/tcp_client.rb +11 -21
- data/lib/rmodbus/tcp_server.rb +46 -44
- data/spec/ext_spec.rb +5 -0
- data/spec/logging_spec.rb +8 -6
- data/spec/read_rtu_response_spec.rb +93 -0
- data/spec/rtu_client_spec.rb +8 -3
- data/spec/rtu_via_tcp_client_spec.rb +72 -0
- data/spec/tcp_client_spec.rb +3 -3
- metadata +47 -23
data/AUTHORS
CHANGED
data/ChangeLog
CHANGED
@@ -54,3 +54,29 @@
|
|
54
54
|
SerialPort::NONE, SerialPort::EVEN, SerialPort::ODD
|
55
55
|
2010-01-20 Timin Aleksey <atimin@gmail.com>
|
56
56
|
* lib/rmodbus/ext.rb: Fixed bug for divisible 8 data in Array#pack_to_word
|
57
|
+
2010-02-6 Timin Aleksey <atimin@gmail.com>
|
58
|
+
* lib/rmodbus/client.rb, lib/rmodbus/tcp_client.rb: Client#connection_retries don't use more
|
59
|
+
2010-07-16 Timin Aleksey <atimin@gmail.com>
|
60
|
+
* lib/rmodbus/rtu_client.rb: Added option read_timeout and accessor RTUClient#read_timeout
|
61
|
+
2010-07-28 Kelley Reynolds <kelley@insidesystems.net>
|
62
|
+
* lib/rmodbus/rtu_via_tcp_client.rb: Added the ability to send RTU commands via TCP Serial Gateway
|
63
|
+
2010-07-31 Kelley Reynolds <kelley@insidesystems.net>
|
64
|
+
* examples/use_rtu_via_tcp_modbus.rb: Added example for using RTUViaTCP client and server
|
65
|
+
* examples/use_tcp_modbus.rb: Misc cleanups
|
66
|
+
* lib/rmodbus.rb: Include the RTUViaTCPServer and Common files
|
67
|
+
* lib/rmodbus/client.rb: Move logging to Common
|
68
|
+
* lib/rmodbus/common.rb: File which houses common logging methods
|
69
|
+
* lib/rmodbus/rtu_client.rb: Logging updates
|
70
|
+
* lib/rmodbus/rtu_via_tcp_client.rb: Added RTUViaTCPClient#read_modbus_rtu_response() to abstract reading the correct number of bytes for RTU via TCP
|
71
|
+
* lib/rmodbus/rtu_via_tcp_server.rb: Added RTUViaTCPServer for use with testing RTUViaTCPClient
|
72
|
+
* lib/rmodbus/tcp_client.rb: Logging updates
|
73
|
+
* lib/rmodbus/tcp_server.rb: Logging updates
|
74
|
+
* spec/logging_spec.rb: Updated to new logging
|
75
|
+
2011-02-02 Timin Aleksey <atimin@gmail>
|
76
|
+
* lib/rmodbus/client.rb, lib/rmodbus/rtu_client.rb : Remove RTUViaTCPClient#read_mb_rtu_response -> Client#read_rtu_response. Added support for exception and mask sunction.
|
77
|
+
* lib/rmodbus/rtu_client.rb : use #read_rtu_response for read pdu
|
78
|
+
* spec/read_rtu_response_spec.rb : add test for Client#read_rtu_response method
|
79
|
+
* spec/rtu_via_tcp_client_spec.rb : add test for RTUViaTCP class
|
80
|
+
2011-02-10 Timin Aleksey <atimin@gmail.com>
|
81
|
+
* lib/rmodbus/client.rb : Fixed retry of request after end timeout. Added property #read_retry_timeout.
|
82
|
+
* rmodbus.gemspec : New 0.5.0 release
|
data/README
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
== Features
|
6
6
|
|
7
7
|
* Support Ruby 1.8, Ruby 1.9
|
8
|
-
* Support
|
8
|
+
* Support TCP, RTU, RTU over TCP protocols
|
9
9
|
* Support client(master) and server(slave)
|
10
10
|
* Support functions:
|
11
11
|
* 01 (0x01) Read Coils
|
@@ -28,11 +28,13 @@ $ gem install --remote rmodbus
|
|
28
28
|
|
29
29
|
require 'rmodbus'
|
30
30
|
|
31
|
-
cl = ModBus::TCPClient.new('127.0.0.1', 8502, 1)
|
31
|
+
cl = ModBus::TCPClient.new('127.0.0.1', 8502, 1) do |cl|
|
32
32
|
|
33
|
-
puts cl.read_holding_registers(0,4)
|
33
|
+
puts cl.read_holding_registers(0,4)
|
34
34
|
|
35
|
-
cl.write_multiple_registers(0, [4,4,4])
|
35
|
+
cl.write_multiple_registers(0, [4,4,4])
|
36
|
+
|
37
|
+
end
|
36
38
|
|
37
39
|
== GitHub
|
38
40
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib/')
|
2
|
+
require 'rmodbus'
|
3
|
+
|
4
|
+
srv = ModBus::RTUViaTCPServer.new(10002,1)
|
5
|
+
srv.coils = [1,0,1,1]
|
6
|
+
srv.discrete_inputs = [1,1,0,0]
|
7
|
+
srv.holding_registers = [1,2,3,4]
|
8
|
+
srv.input_registers = [1,2,3,4]
|
9
|
+
srv.debug = true
|
10
|
+
srv.start
|
11
|
+
|
12
|
+
ModBus::RTUViaTCPClient.connect('127.0.0.1', 10002, 1) do |cl|
|
13
|
+
cl.debug = true
|
14
|
+
puts cl.read_holding_registers(0,4).inspect
|
15
|
+
cl.write_multiple_registers(0, [4,4,4])
|
16
|
+
puts cl.read_holding_registers(0,4).inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
srv.shutdown
|
data/examples/use_tcp_modbus.rb
CHANGED
@@ -10,9 +10,10 @@ srv.debug = true
|
|
10
10
|
srv.audit = true
|
11
11
|
srv.start
|
12
12
|
|
13
|
-
|
14
|
-
cl.debug = true
|
15
|
-
puts cl.read_holding_registers(0,4)
|
16
|
-
cl.write_multiple_registers(0, [4,4,4])
|
17
|
-
puts cl.read_holding_registers(0,4)
|
13
|
+
ModBus::TCPClient.connect('127.0.0.1', 8502, 1) do |cl|
|
14
|
+
cl.debug = true
|
15
|
+
puts cl.read_holding_registers(0,4)
|
16
|
+
cl.write_multiple_registers(0, [4,4,4])
|
17
|
+
puts cl.read_holding_registers(0,4)
|
18
|
+
end
|
18
19
|
srv.stop
|
data/lib/rmodbus.rb
CHANGED
@@ -9,8 +9,11 @@
|
|
9
9
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
10
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
11
|
# GNU General Public License for more details.
|
12
|
+
require 'rmodbus/common'
|
12
13
|
require 'rmodbus/tcp_client'
|
13
14
|
require 'rmodbus/tcp_server'
|
14
15
|
require 'rmodbus/rtu_client'
|
15
16
|
require 'rmodbus/rtu_server'
|
17
|
+
require 'rmodbus/rtu_via_tcp_client'
|
18
|
+
require 'rmodbus/rtu_via_tcp_server'
|
16
19
|
|
data/lib/rmodbus/client.rb
CHANGED
@@ -12,21 +12,42 @@
|
|
12
12
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
13
|
# GNU General Public License for more details.
|
14
14
|
|
15
|
+
require 'rmodbus/common'
|
15
16
|
require 'rmodbus/exceptions'
|
16
17
|
require 'rmodbus/ext'
|
17
18
|
|
18
19
|
|
19
20
|
module ModBus
|
20
|
-
|
21
21
|
class Client
|
22
|
-
|
23
22
|
include Errors
|
23
|
+
include Common
|
24
24
|
# Number of times to retry on connection and read timeouts
|
25
|
-
attr_accessor :
|
25
|
+
attr_accessor :read_retries, :read_retry_timeout
|
26
|
+
|
27
|
+
def connection_retries
|
28
|
+
warn "[DEPRECATION] `connection_retries` is deprecated. Please don't use it."
|
29
|
+
@connection_retries
|
30
|
+
end
|
26
31
|
|
32
|
+
def connection_retries=(value)
|
33
|
+
warn "[DEPRECATION] `connection_retries=` is deprecated. Please don't use it."
|
34
|
+
@connection_retries = value
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
Exceptions = {
|
39
|
+
1 => IllegalFunction.new("The function code received in the query is not an allowable action for the server"),
|
40
|
+
2 => IllegalDataAddress.new("The data address received in the query is not an allowable address for the server"),
|
41
|
+
3 => IllegalDataValue.new("A value contained in the query data field is not an allowable value for server"),
|
42
|
+
4 => SlaveDeviceFailure.new("An unrecoverable error occurred while the server was attempting to perform the requested action"),
|
43
|
+
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"),
|
44
|
+
6 => SlaveDeviceBus.new("The server is engaged in processing a long duration program command"),
|
45
|
+
8 => MemoryParityError.new("The extended file area failed to pass a consistency check")
|
46
|
+
}
|
27
47
|
def initialize
|
28
48
|
@connection_retries = 10
|
29
49
|
@read_retries = 10
|
50
|
+
@read_retry_timeout = 1
|
30
51
|
end
|
31
52
|
# Read value *ncoils* coils starting with *addr*
|
32
53
|
#
|
@@ -44,7 +65,7 @@ module ModBus
|
|
44
65
|
|
45
66
|
# Deprecated version of read_discrete_inputs
|
46
67
|
def read_discret_inputs(addr, ncoils)
|
47
|
-
warn "[DEPRECATION] `read_discret_inputs` is deprecated. Please use `read_discrete_inputs` instead."
|
68
|
+
#warn "[DEPRECATION] `read_discret_inputs` is deprecated. Please use `read_discrete_inputs` instead."
|
48
69
|
read_discrete_inputs(addr, ncoils)
|
49
70
|
end
|
50
71
|
|
@@ -131,12 +152,14 @@ module ModBus
|
|
131
152
|
end
|
132
153
|
|
133
154
|
def query(pdu)
|
134
|
-
send_pdu(pdu)
|
135
|
-
|
136
155
|
tried = 0
|
137
156
|
begin
|
138
|
-
timeout(
|
157
|
+
timeout(@read_retry_timeout, ModBusTimeout) do
|
158
|
+
send_pdu(pdu)
|
159
|
+
pdu = read_pdu
|
160
|
+
end
|
139
161
|
rescue ModBusTimeout => err
|
162
|
+
log "Timeout of read operation: (#{@read_retries - tried})"
|
140
163
|
tried += 1
|
141
164
|
retry unless tried >= @read_retries
|
142
165
|
raise ModBusTimeout.new, "Timed out during read attempt"
|
@@ -145,29 +168,16 @@ module ModBus
|
|
145
168
|
return nil if pdu.size == 0
|
146
169
|
|
147
170
|
if pdu.getbyte(0) >= 0x80
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
raise IllegalDataAddress.new, "The data address received in the query is not an allowable address for the server"
|
153
|
-
when 3
|
154
|
-
raise IllegalDataValue.new, "A value contained in the query data field is not an allowable value for server"
|
155
|
-
when 4
|
156
|
-
raise SlaveDeviceFailure.new, "An unrecoverable error occurred while the server was attempting to perform the requested action"
|
157
|
-
when 5
|
158
|
-
raise Acknowledge.new, "The server has accepted the request and is processing it, but a long duration of time will be required to do so"
|
159
|
-
when 6
|
160
|
-
raise SlaveDeviceBus.new, "The server is engaged in processing a long duration program command"
|
161
|
-
when 8
|
162
|
-
raise MemoryParityError.new, "The extended file area failed to pass a consistency check"
|
163
|
-
else
|
164
|
-
raise ModBusException.new, "Unknown error"
|
165
|
-
end
|
171
|
+
exc_id = pdu.getbyte(1)
|
172
|
+
raise Exceptions[exc_id] unless Exceptions[exc_id].nil?
|
173
|
+
|
174
|
+
raise ModBusException.new, "Unknown error"
|
166
175
|
end
|
167
176
|
pdu[2..-1]
|
168
177
|
end
|
169
178
|
|
170
179
|
protected
|
180
|
+
|
171
181
|
def send_pdu(pdu)
|
172
182
|
end
|
173
183
|
|
@@ -177,20 +187,27 @@ module ModBus
|
|
177
187
|
def close
|
178
188
|
end
|
179
189
|
|
180
|
-
|
181
|
-
def
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
190
|
+
# We have to read specific amounts of numbers of bytes from the network depending on the function code and content
|
191
|
+
def read_rtu_response(io)
|
192
|
+
# Read the slave_id and function code
|
193
|
+
msg = io.read(2)
|
194
|
+
function_code = msg.getbyte(1)
|
195
|
+
case function_code
|
196
|
+
when 1,2,3,4 then
|
197
|
+
# read the third byte to find out how much more
|
198
|
+
# we need to read + CRC
|
199
|
+
msg += io.read(1)
|
200
|
+
msg += io.read(msg.getbyte(2)+2)
|
201
|
+
when 5,6,15,16 then
|
202
|
+
# We just read in an additional 6 bytes
|
203
|
+
msg += io.read(6)
|
204
|
+
when 22 then
|
205
|
+
msg += io.read(8)
|
206
|
+
when 0x80..0xff then
|
207
|
+
msg += io.read(4)
|
186
208
|
else
|
187
|
-
|
188
|
-
end
|
189
|
-
result << "[#{byte}]"
|
209
|
+
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
190
210
|
end
|
191
|
-
result
|
192
211
|
end
|
193
|
-
|
194
212
|
end
|
195
|
-
|
196
213
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ModBus
|
2
|
+
module Common
|
3
|
+
|
4
|
+
private
|
5
|
+
def log(msg)
|
6
|
+
$stdout.puts msg if @debug
|
7
|
+
end
|
8
|
+
|
9
|
+
def logging_bytes(msg)
|
10
|
+
result = ""
|
11
|
+
msg.each_byte do |c|
|
12
|
+
byte = if c < 16
|
13
|
+
'0' + c.to_s(16)
|
14
|
+
else
|
15
|
+
c.to_s(16)
|
16
|
+
end
|
17
|
+
result << "[#{byte}]"
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
data/lib/rmodbus/ext.rb
CHANGED
data/lib/rmodbus/rtu_client.rb
CHANGED
@@ -24,7 +24,7 @@ module ModBus
|
|
24
24
|
class RTUClient < Client
|
25
25
|
|
26
26
|
include CRC16
|
27
|
-
attr_reader :port, :baud, :slave, :data_bits, :stop_bits, :parity
|
27
|
+
attr_reader :port, :baud, :slave, :data_bits, :stop_bits, :parity, :read_timeout
|
28
28
|
attr_accessor :debug
|
29
29
|
|
30
30
|
# Connect with RTU server
|
@@ -81,19 +81,22 @@ module ModBus
|
|
81
81
|
# :stop_bits => 1 or 2
|
82
82
|
#
|
83
83
|
# :parity => NONE, EVEN or ODD
|
84
|
+
#
|
85
|
+
# :read_timeout => default 5 ms
|
84
86
|
def initialize(port, baud=9600, slaveaddr=1, options = {})
|
85
87
|
@port, @baud, @slave = port, baud, slaveaddr
|
86
88
|
|
87
|
-
@data_bits, @stop_bits, @parity = 8, 1, SerialPort::NONE
|
89
|
+
@data_bits, @stop_bits, @parity, @read_timeout = 8, 1, SerialPort::NONE, 5
|
88
90
|
|
89
91
|
@data_bits = options[:data_bits] unless options[:data_bits].nil?
|
90
92
|
@stop_bits = options[:stop_bits] unless options[:stop_bits].nil?
|
91
93
|
@parity = options[:parity] unless options[:parity].nil?
|
94
|
+
@read_timeout = options[:read_timeout] unless options[:read_timeout].nil?
|
92
95
|
|
93
96
|
@debug = false
|
94
97
|
|
95
98
|
@sp = SerialPort.new(@port, @baud, @data_bits, @stop_bits, @parity)
|
96
|
-
@sp.read_timeout =
|
99
|
+
@sp.read_timeout = @read_timeout
|
97
100
|
|
98
101
|
super()
|
99
102
|
end
|
@@ -111,23 +114,20 @@ module ModBus
|
|
111
114
|
msg = @slave.chr + pdu
|
112
115
|
msg << crc16(msg).to_word
|
113
116
|
@sp.write msg
|
114
|
-
|
115
|
-
|
116
|
-
end
|
117
|
+
|
118
|
+
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
117
119
|
end
|
118
120
|
|
119
121
|
def read_pdu
|
120
|
-
|
121
|
-
while msg.size == 0
|
122
|
-
msg = @sp.read
|
123
|
-
end
|
122
|
+
msg = read_rtu_response(@sp)
|
124
123
|
|
125
|
-
|
126
|
-
STDOUT << "Rx (#{msg.size} bytes): " + logging_bytes(msg) + "\n"
|
127
|
-
end
|
124
|
+
log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
|
128
125
|
|
129
126
|
if msg.getbyte(0) == @slave
|
130
127
|
return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
128
|
+
log "Ignore package: don't match CRC"
|
129
|
+
else
|
130
|
+
log "Ignore package: don't match slave ID"
|
131
131
|
end
|
132
132
|
loop do
|
133
133
|
#waite timeout
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 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
|
+
require 'rmodbus/crc16'
|
16
|
+
require 'timeout'
|
17
|
+
require 'rmodbus/client'
|
18
|
+
require 'rmodbus/exceptions'
|
19
|
+
|
20
|
+
module ModBus
|
21
|
+
|
22
|
+
class RTUViaTCPClient < Client
|
23
|
+
|
24
|
+
include CRC16
|
25
|
+
attr_reader :ipaddr, :port, :slave
|
26
|
+
attr_accessor :debug
|
27
|
+
|
28
|
+
# Connect with Serial TCP Gateway (eg barionet-50)
|
29
|
+
#
|
30
|
+
# ipaddr - ip of the server
|
31
|
+
#
|
32
|
+
# port - port TCP connections
|
33
|
+
#
|
34
|
+
# slaveaddr - slave ID of the server
|
35
|
+
#
|
36
|
+
# RTUViaTCPClient.connect('127.0.0.1') do |cl|
|
37
|
+
#
|
38
|
+
# put cl.read_holding_registers(0, 10)
|
39
|
+
#
|
40
|
+
# end
|
41
|
+
def self.connect(ipaddr, port = 10002, slaveaddr = 1)
|
42
|
+
cl = RTUViaTCPClient.new(ipaddr, port, slaveaddr)
|
43
|
+
yield cl
|
44
|
+
cl.close
|
45
|
+
end
|
46
|
+
|
47
|
+
# Connect with a ModBus server
|
48
|
+
#
|
49
|
+
# ipaddr - ip of the server
|
50
|
+
#
|
51
|
+
# port - port TCP connections
|
52
|
+
#
|
53
|
+
# slaveaddr - slave ID of the server
|
54
|
+
def initialize(ipaddr, port = 10002, slaveaddr = 1)
|
55
|
+
@ipaddr, @port = ipaddr, port
|
56
|
+
tried = 0
|
57
|
+
begin
|
58
|
+
timeout(1, ModBusTimeout) do
|
59
|
+
@sock = TCPSocket.new(@ipaddr, @port)
|
60
|
+
end
|
61
|
+
rescue ModBusTimeout => err
|
62
|
+
raise ModBusTimeout.new, 'Timed out attempting to create connection'
|
63
|
+
end
|
64
|
+
@slave = slaveaddr
|
65
|
+
@debug = false
|
66
|
+
super()
|
67
|
+
end
|
68
|
+
|
69
|
+
# Close TCP connections
|
70
|
+
def close
|
71
|
+
@sock.close unless @sock.closed?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Check TCP connections
|
75
|
+
def closed?
|
76
|
+
@sock.closed?
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def send_pdu(pdu)
|
82
|
+
msg = @slave.chr + pdu
|
83
|
+
msg << crc16(msg).to_word
|
84
|
+
@sock.write msg
|
85
|
+
|
86
|
+
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
87
|
+
end
|
88
|
+
|
89
|
+
def read_pdu
|
90
|
+
# Read the response appropriately
|
91
|
+
msg = read_rtu_response(@sock)
|
92
|
+
|
93
|
+
log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
|
94
|
+
if msg.getbyte(0) == @slave
|
95
|
+
return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
96
|
+
log "Ignore package: don't match CRC"
|
97
|
+
else
|
98
|
+
log "Ignore package: don't match slave ID"
|
99
|
+
end
|
100
|
+
loop do
|
101
|
+
#waite timeout
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 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
|
+
require 'rmodbus/parsers'
|
17
|
+
require 'gserver'
|
18
|
+
|
19
|
+
module ModBus
|
20
|
+
|
21
|
+
class RTUViaTCPServer < GServer
|
22
|
+
include Parsers
|
23
|
+
include CRC16
|
24
|
+
include Common
|
25
|
+
|
26
|
+
attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers, :debug
|
27
|
+
|
28
|
+
def discret_inputs
|
29
|
+
warn "[DEPRECATION] `discret_inputs` is deprecated. Please use `discrete_inputs` instead."
|
30
|
+
@discrete_inputs
|
31
|
+
end
|
32
|
+
|
33
|
+
def discret_inputs=(val)
|
34
|
+
warn "[DEPRECATION] `discret_inputs=` is deprecated. Please use `discrete_inputs=` instead."
|
35
|
+
@discrete_inputs=val
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def initialize(port = 10002, slave = 1)
|
40
|
+
@coils = []
|
41
|
+
@discrete_inputs = []
|
42
|
+
@holding_registers =[]
|
43
|
+
@input_registers = []
|
44
|
+
@slave = slave
|
45
|
+
super(port)
|
46
|
+
end
|
47
|
+
|
48
|
+
def serve(io)
|
49
|
+
loop do
|
50
|
+
# read the RTU message
|
51
|
+
msg = read_modbus_rtu_request(io)
|
52
|
+
|
53
|
+
# If there is no RTU message, we're done serving this client
|
54
|
+
break if msg.nil?
|
55
|
+
|
56
|
+
if msg.getbyte(0) == @slave and msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
57
|
+
pdu = exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
|
58
|
+
resp = @slave.chr + pdu
|
59
|
+
resp << crc16(resp).to_word
|
60
|
+
log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
|
61
|
+
io.write resp
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# We have to read specific amounts of numbers of bytes from the network depending on the function code and content
|
69
|
+
# NOTE: The initial read could be increased to 7 and that would let us cobine the two reads for functions 15 and 16 but this method is more clear
|
70
|
+
def read_modbus_rtu_request(io)
|
71
|
+
# Read the slave_id and function code
|
72
|
+
msg = io.read(2)
|
73
|
+
|
74
|
+
# If msg is nil, then our client never sent us anything and it's time to disconnect
|
75
|
+
return if msg.nil?
|
76
|
+
|
77
|
+
function_code = msg.getbyte(1)
|
78
|
+
if [1, 2, 3, 4, 5, 6].include?(function_code)
|
79
|
+
# read 6 more bytes and return the message total message
|
80
|
+
msg += io.read(6)
|
81
|
+
elsif [15, 16].include?(function_code)
|
82
|
+
# Read in first register, register count, and data bytes
|
83
|
+
msg += io.read(5)
|
84
|
+
# Read in however much data we need to + 2 CRC bytes
|
85
|
+
msg += io.read(msg.getbyte(6) + 2)
|
86
|
+
else
|
87
|
+
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
88
|
+
end
|
89
|
+
|
90
|
+
log "Server RX (#{msg.size} bytes): #{logging_bytes(msg)}"
|
91
|
+
|
92
|
+
msg
|
93
|
+
end
|
94
|
+
|
95
|
+
def log(msg)
|
96
|
+
if @debug
|
97
|
+
$stdout.puts msg
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def logging_bytes(msg)
|
102
|
+
result = ""
|
103
|
+
msg.each_byte do |c|
|
104
|
+
byte = if c < 16
|
105
|
+
'0' + c.to_s(16)
|
106
|
+
else
|
107
|
+
c.to_s(16)
|
108
|
+
end
|
109
|
+
result << "[#{byte}]"
|
110
|
+
end
|
111
|
+
result
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/rmodbus/tcp_client.rb
CHANGED
@@ -23,10 +23,8 @@ module ModBus
|
|
23
23
|
|
24
24
|
include Timeout
|
25
25
|
|
26
|
-
attr_reader :ipaddr, :port, :slave
|
26
|
+
attr_reader :ipaddr, :port, :slave, :transaction
|
27
27
|
attr_accessor :debug
|
28
|
-
|
29
|
-
@@transaction = 0
|
30
28
|
|
31
29
|
# Connect with ModBus server
|
32
30
|
#
|
@@ -55,15 +53,14 @@ module ModBus
|
|
55
53
|
#
|
56
54
|
# slaveaddr - slave ID of the server
|
57
55
|
def initialize(ipaddr, port = 502, slaveaddr = 1)
|
56
|
+
@transaction = 0
|
58
57
|
@ipaddr, @port = ipaddr, port
|
59
58
|
tried = 0
|
60
59
|
begin
|
61
60
|
timeout(1, ModBusTimeout) do
|
62
|
-
|
63
|
-
|
61
|
+
@sock = TCPSocket.new(@ipaddr, @port)
|
62
|
+
end
|
64
63
|
rescue ModBusTimeout => err
|
65
|
-
tried += 1
|
66
|
-
retry unless tried >= @connection_retries
|
67
64
|
raise ModBusTimeout.new, 'Timed out attempting to create connection'
|
68
65
|
end
|
69
66
|
@slave = slaveaddr
|
@@ -81,32 +78,25 @@ module ModBus
|
|
81
78
|
@sock.closed?
|
82
79
|
end
|
83
80
|
|
84
|
-
def self.transaction
|
85
|
-
@@transaction
|
86
|
-
end
|
87
|
-
|
88
81
|
private
|
89
82
|
def send_pdu(pdu)
|
90
|
-
|
91
|
-
|
92
|
-
msg =
|
83
|
+
@transaction = 0 if @transaction.next > 65535
|
84
|
+
@transaction += 1
|
85
|
+
msg = @transaction.to_word + "\0\0" + (pdu.size + 1).to_word + @slave.chr + pdu
|
93
86
|
@sock.write msg
|
94
87
|
|
95
|
-
|
96
|
-
STDOUT << "Tx (#{msg.size} bytes): " + logging_bytes(msg) + "\n"
|
97
|
-
end
|
88
|
+
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
98
89
|
end
|
99
90
|
|
100
91
|
def read_pdu
|
101
92
|
header = @sock.read(7)
|
102
93
|
if header
|
103
94
|
tin = header[0,2].unpack('n')[0]
|
104
|
-
raise Errors::ModBusException.new("Transaction number mismatch") unless tin ==
|
95
|
+
raise Errors::ModBusException.new("Transaction number mismatch") unless tin == @transaction
|
105
96
|
len = header[4,2].unpack('n')[0]
|
106
97
|
msg = @sock.read(len-1)
|
107
|
-
|
108
|
-
|
109
|
-
end
|
98
|
+
|
99
|
+
log "Rx (#{(header + msg).size} bytes): " + logging_bytes(header + msg)
|
110
100
|
msg
|
111
101
|
else
|
112
102
|
raise Errors::ModBusException.new("Server did not respond")
|
data/lib/rmodbus/tcp_server.rb
CHANGED
@@ -14,49 +14,51 @@
|
|
14
14
|
require 'rmodbus/parsers'
|
15
15
|
require 'gserver'
|
16
16
|
|
17
|
-
|
18
17
|
module ModBus
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
18
|
+
class TCPServer < GServer
|
19
|
+
include Parsers
|
20
|
+
include Common
|
21
|
+
|
22
|
+
attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers, :debug
|
23
|
+
|
24
|
+
def discret_inputs
|
25
|
+
warn "[DEPRECATION] `discret_inputs` is deprecated. Please use `discrete_inputs` instead."
|
26
|
+
@discrete_inputs
|
27
|
+
end
|
28
|
+
|
29
|
+
def discret_inputs=(val)
|
30
|
+
warn "[DEPRECATION] `discret_inputs=` is deprecated. Please use `discrete_inputs=` instead."
|
31
|
+
@discrete_inputs=val
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(port = 502, uid = 1)
|
35
|
+
@coils = []
|
36
|
+
@discrete_inputs = []
|
37
|
+
@holding_registers =[]
|
38
|
+
@input_registers = []
|
39
|
+
@uid = uid
|
40
|
+
super(port)
|
41
|
+
end
|
42
|
+
|
43
|
+
def serve(io)
|
44
|
+
loop do
|
45
|
+
req = io.read(7)
|
46
|
+
if req[2,2] != "\x00\x00" or req.getbyte(6) != @uid
|
47
|
+
io.close
|
48
|
+
break
|
49
|
+
end
|
50
|
+
|
51
|
+
tr = req[0,2]
|
52
|
+
len = req[4,2].unpack('n')[0]
|
53
|
+
req = io.read(len - 1)
|
54
|
+
log "Server RX (#{req.size} bytes): #{logging_bytes(req)}"
|
55
|
+
|
56
|
+
pdu = exec_req(req, @coils, @discrete_inputs, @holding_registers, @input_registers)
|
57
|
+
|
58
|
+
resp = tr + "\0\0" + (pdu.size + 1).to_word + @uid.chr + pdu
|
59
|
+
log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
|
60
|
+
io.write resp
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
62
64
|
end
|
data/spec/ext_spec.rb
CHANGED
@@ -4,6 +4,7 @@ describe Array do
|
|
4
4
|
|
5
5
|
before do
|
6
6
|
@arr = [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
|
7
|
+
@test = [0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0]
|
7
8
|
end
|
8
9
|
|
9
10
|
it "should return string reprisent 16bit" do
|
@@ -13,6 +14,10 @@ describe Array do
|
|
13
14
|
it "fixed bug for divisible 8 data " do
|
14
15
|
([0] * 8).pack_to_word.should == "\x00"
|
15
16
|
end
|
17
|
+
|
18
|
+
it "should unpack to @test" do
|
19
|
+
"test".unpack_bits == @test
|
20
|
+
end
|
16
21
|
|
17
22
|
end
|
18
23
|
|
data/spec/logging_spec.rb
CHANGED
@@ -20,8 +20,8 @@ describe TCPClient do
|
|
20
20
|
request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
|
21
21
|
mock_query(request,response)
|
22
22
|
@mb_client.debug = true
|
23
|
-
|
24
|
-
|
23
|
+
$stdout.should_receive(:puts).with("Tx (12 bytes): [00][01][00][00][00][06][01][03][00][6b][00][03]")
|
24
|
+
$stdout.should_receive(:puts).with("Rx (15 bytes): [00][01][00][00][00][09][01][03][06][02][2b][00][00][00][64]")
|
25
25
|
@mb_client.query(request)
|
26
26
|
end
|
27
27
|
|
@@ -33,7 +33,7 @@ describe TCPClient do
|
|
33
33
|
|
34
34
|
|
35
35
|
def mock_query(request, response)
|
36
|
-
@adu =
|
36
|
+
@adu = @mb_client.transaction.next.to_word + "\x0\x0\x0\x9" + UID.chr + request
|
37
37
|
@sock.should_receive(:write).with(@adu[0,4] + "\0\6" + UID.chr + request)
|
38
38
|
@sock.should_receive(:read).with(7).and_return(@adu[0,7])
|
39
39
|
@sock.should_receive(:read).with(8).and_return(response)
|
@@ -56,11 +56,13 @@ describe RTUClient do
|
|
56
56
|
it 'should log rec\send bytes' do
|
57
57
|
request = "\x3\x0\x1\x0\x1"
|
58
58
|
@sp.should_receive(:write).with("\1#{request}\xd5\xca")
|
59
|
-
@sp.should_receive(:read).and_return("\x1\x3
|
59
|
+
@sp.should_receive(:read).with(2).and_return("\x1\x3")
|
60
|
+
@sp.should_receive(:read).with(1).and_return("\x2")
|
61
|
+
@sp.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
|
60
62
|
|
61
63
|
@mb_client.debug = true
|
62
|
-
|
63
|
-
|
64
|
+
$stdout.should_receive(:puts).with("Tx (8 bytes): [01][03][00][01][00][01][d5][ca]")
|
65
|
+
$stdout.should_receive(:puts).with("Rx (7 bytes): [01][03][02][ff][ff][b9][f4]")
|
64
66
|
|
65
67
|
@mb_client.query(request).should == "\xff\xff"
|
66
68
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'rmodbus/client'
|
2
|
+
|
3
|
+
include ModBus
|
4
|
+
|
5
|
+
#Use public wrap method
|
6
|
+
|
7
|
+
class Client
|
8
|
+
def test_read_method(msg)
|
9
|
+
io = TestIO.new(msg)
|
10
|
+
read_rtu_response(io)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class TestIO
|
16
|
+
def initialize(msg)
|
17
|
+
@msg = msg
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(num)
|
21
|
+
result = @msg[0,num]
|
22
|
+
@msg = @msg[num..-1]
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#read_rtu_response" do
|
28
|
+
before do
|
29
|
+
@cl_mb = Client.new
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should read response for 'read coils'" do
|
33
|
+
resp = make_resp("\x1\x3\xcd\x6b\x05")
|
34
|
+
@cl_mb.test_read_method(resp).should == resp
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should read response for 'read discrete inputs'" do
|
38
|
+
resp = make_resp("\x2\x3\xac\xdb\x35")
|
39
|
+
@cl_mb.test_read_method(resp).should == resp
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should read response for 'read holding registers'" do
|
43
|
+
resp = make_resp("\x3\x6\x2\x2b\x0\x0\x0\x64")
|
44
|
+
@cl_mb.test_read_method(resp).should == resp
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should read response for 'read input registers'" do
|
48
|
+
resp = make_resp("\x4\x2\x0\xa")
|
49
|
+
@cl_mb.test_read_method(resp).should == resp
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should read response for 'write single coil'" do
|
53
|
+
resp = make_resp("\x5\x0\xac\xff\x0")
|
54
|
+
@cl_mb.test_read_method(resp).should == resp
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should read response for 'write single register'" do
|
58
|
+
resp = make_resp("\x6\x0\x1\x0\x3")
|
59
|
+
@cl_mb.test_read_method(resp).should == resp
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should read response for 'write multiple coils'" do
|
63
|
+
resp = make_resp("\xf\x0\x13\x0\xa")
|
64
|
+
@cl_mb.test_read_method(resp).should == resp
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should read response for 'write multiple registers'" do
|
68
|
+
resp = make_resp("\x10\x0\x1\x0\x2")
|
69
|
+
@cl_mb.test_read_method(resp).should == resp
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should read response 'mask write register'" do
|
73
|
+
resp = make_resp("\x16\x0\x4\x0\xf2\x0\x25")
|
74
|
+
@cl_mb.test_read_method(resp).should == resp
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should read exception codes" do
|
78
|
+
resp = make_resp("\x84\x3")
|
79
|
+
@cl_mb.test_read_method(resp).should == resp
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should raise exception if function is illegal" do
|
83
|
+
resp = make_resp("\xff\x0\x1\x0\x2").should raise_error {
|
84
|
+
ModBus::Errors::IllegalFunction
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def make_resp(msg)
|
90
|
+
"\x1" + msg + "\x2\x2" # slave + msg + mock_crc
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
data/spec/rtu_client_spec.rb
CHANGED
@@ -12,6 +12,7 @@ describe RTUClient do
|
|
12
12
|
@sp = mock('Serial port')
|
13
13
|
SerialPort.should_receive(:new).with("/dev/port1", 9600, 8, 1, 0).and_return(@sp)
|
14
14
|
@sp.stub!(:read_timeout=)
|
15
|
+
@sp.stub!(:read)
|
15
16
|
|
16
17
|
@mb_client = RTUClient.new("/dev/port1", 9600, 1,
|
17
18
|
:data_bits => 8, :stop_bits => 1, :parity => SerialPort::NONE)
|
@@ -21,21 +22,25 @@ describe RTUClient do
|
|
21
22
|
it "should ignore frame with other UID" do
|
22
23
|
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
23
24
|
@sp.should_receive(:write).with("\1#{request}\xA6\x31")
|
24
|
-
@sp.should_receive(:read).and_return("\x2\x10
|
25
|
+
@sp.should_receive(:read).with(2).and_return("\x2\x10")
|
26
|
+
@sp.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
25
27
|
lambda {@mb_client.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
26
28
|
end
|
27
29
|
|
28
30
|
it "should ignored frame with incorrect CRC" do
|
29
31
|
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
30
32
|
@sp.should_receive(:write).with("\1#{request}\xA6\x31")
|
31
|
-
@sp.should_receive(:read).and_return("\
|
33
|
+
@sp.should_receive(:read).with(2).and_return("\x2\x10")
|
34
|
+
@sp.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
32
35
|
lambda {@mb_client.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
33
36
|
end
|
34
37
|
|
35
38
|
it "should return value of registers"do
|
36
39
|
request = "\x3\x0\x1\x0\x1"
|
37
40
|
@sp.should_receive(:write).with("\1#{request}\xd5\xca")
|
38
|
-
@sp.should_receive(:read).and_return("\x1\x3
|
41
|
+
@sp.should_receive(:read).with(2).and_return("\x1\x3")
|
42
|
+
@sp.should_receive(:read).with(1).and_return("\x2")
|
43
|
+
@sp.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
|
39
44
|
@mb_client.query(request).should == "\xff\xff"
|
40
45
|
end
|
41
46
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
rescue
|
4
|
+
end
|
5
|
+
require 'rmodbus'
|
6
|
+
|
7
|
+
include ModBus
|
8
|
+
|
9
|
+
describe RTUViaTCPClient do
|
10
|
+
|
11
|
+
before do
|
12
|
+
@sock = mock('Socked')
|
13
|
+
TCPSocket.should_receive(:new).with("127.0.0.1", 10002).and_return(@sock)
|
14
|
+
@sock.stub!(:read_timeout=)
|
15
|
+
@sock.stub!(:read)
|
16
|
+
|
17
|
+
@mb_client = RTUViaTCPClient.new("127.0.0.1")
|
18
|
+
@mb_client.read_retries = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should ignore frame with other UID" do
|
22
|
+
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
23
|
+
@sock.should_receive(:write).with("\1#{request}\xA6\x31")
|
24
|
+
@sock.should_receive(:read).with(2).and_return("\x2\x10")
|
25
|
+
@sock.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
26
|
+
lambda {@mb_client.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should ignored frame with incorrect CRC" do
|
30
|
+
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
31
|
+
@sock.should_receive(:write).with("\1#{request}\xA6\x31")
|
32
|
+
@sock.should_receive(:read).with(2).and_return("\x2\x10")
|
33
|
+
@sock.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
34
|
+
lambda {@mb_client.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return value of registers"do
|
38
|
+
request = "\x3\x0\x1\x0\x1"
|
39
|
+
@sock.should_receive(:write).with("\1#{request}\xd5\xca")
|
40
|
+
@sock.should_receive(:read).with(2).and_return("\x1\x3")
|
41
|
+
@sock.should_receive(:read).with(1).and_return("\x2")
|
42
|
+
@sock.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
|
43
|
+
@mb_client.query(request).should == "\xff\xff"
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should sugar connect method' do
|
47
|
+
ipaddr, port, slave = '127.0.0.1', 502, 3
|
48
|
+
TCPSocket.should_receive(:new).with(ipaddr, port).and_return(@sock)
|
49
|
+
@sock.should_receive(:closed?).and_return(false)
|
50
|
+
@sock.should_receive(:close)
|
51
|
+
RTUViaTCPClient.connect(ipaddr, port, slave) do |cl|
|
52
|
+
cl.ipaddr.should == ipaddr
|
53
|
+
cl.port.should == port
|
54
|
+
cl.slave.should == slave
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should have closed? method' do
|
59
|
+
@sock.should_receive(:closed?).and_return(false)
|
60
|
+
@mb_client.closed?.should == false
|
61
|
+
|
62
|
+
@sock.should_receive(:closed?).and_return(false)
|
63
|
+
@sock.should_receive(:close)
|
64
|
+
|
65
|
+
@mb_client.close
|
66
|
+
|
67
|
+
@sock.should_receive(:closed?).and_return(true)
|
68
|
+
@mb_client.closed?.should == true
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
data/spec/tcp_client_spec.rb
CHANGED
@@ -17,14 +17,14 @@ describe TCPClient, "method 'query'" do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'should send valid MBAP Header' do
|
20
|
-
@adu[0,2] =
|
20
|
+
@adu[0,2] = @mb_client.transaction.next.to_word
|
21
21
|
@sock.should_receive(:write).with(@adu)
|
22
22
|
@sock.should_receive(:read).with(7).and_return(@adu)
|
23
23
|
@mb_client.query('').should == nil
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'should throw exception if get other transaction' do
|
27
|
-
@adu[0,2] =
|
27
|
+
@adu[0,2] = @mb_client.transaction.next.to_word
|
28
28
|
@sock.should_receive(:write).with(@adu)
|
29
29
|
@sock.should_receive(:read).with(7).and_return("\000\002\000\000\000\001" + UID.chr)
|
30
30
|
begin
|
@@ -37,7 +37,7 @@ describe TCPClient, "method 'query'" do
|
|
37
37
|
it 'should return only data from PDU' do
|
38
38
|
request = "\x3\x0\x6b\x0\x3"
|
39
39
|
response = "\x3\x6\x2\x2b\x0\x0\x0\x64"
|
40
|
-
@adu =
|
40
|
+
@adu = @mb_client.transaction.next.to_word + "\x0\x0\x0\x9" + UID.chr + request
|
41
41
|
@sock.should_receive(:write).with(@adu[0,4] + "\0\6" + UID.chr + request)
|
42
42
|
@sock.should_receive(:read).with(7).and_return(@adu[0,7])
|
43
43
|
@sock.should_receive(:read).with(8).and_return(response)
|
metadata
CHANGED
@@ -1,27 +1,39 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rmodbus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 11
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 5
|
9
|
+
- 0
|
10
|
+
version: 0.5.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
|
-
- A.Timin, J. Sanders
|
13
|
+
- A.Timin, J. Sanders, K. Reynolds
|
8
14
|
autorequire: rmodbus
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2011-02-10 00:00:00 +05:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: serialport
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
29
|
+
hash: 31
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 1.0.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
25
37
|
description:
|
26
38
|
email: atimin@gmail.com
|
27
39
|
executables: []
|
@@ -34,28 +46,34 @@ extra_rdoc_files:
|
|
34
46
|
- LICENSE
|
35
47
|
- ChangeLog
|
36
48
|
files:
|
37
|
-
- lib/rmodbus
|
49
|
+
- lib/rmodbus.rb
|
50
|
+
- lib/rmodbus/client.rb
|
51
|
+
- lib/rmodbus/crc16.rb
|
38
52
|
- lib/rmodbus/parsers.rb
|
39
53
|
- lib/rmodbus/rtu_client.rb
|
54
|
+
- lib/rmodbus/ext.rb
|
55
|
+
- lib/rmodbus/rtu_via_tcp_client.rb
|
40
56
|
- lib/rmodbus/tcp_server.rb
|
41
|
-
- lib/rmodbus/rtu_server.rb
|
42
57
|
- lib/rmodbus/exceptions.rb
|
43
|
-
- lib/rmodbus/
|
44
|
-
- lib/rmodbus/
|
45
|
-
- lib/rmodbus/
|
46
|
-
- lib/rmodbus.rb
|
58
|
+
- lib/rmodbus/rtu_server.rb
|
59
|
+
- lib/rmodbus/rtu_via_tcp_server.rb
|
60
|
+
- lib/rmodbus/common.rb
|
61
|
+
- lib/rmodbus/tcp_client.rb
|
62
|
+
- examples/add_new_function.rb
|
47
63
|
- examples/use_tcp_modbus.rb
|
48
|
-
- examples/perfomance_rtu.rb
|
49
64
|
- examples/perfomance_tcp.rb
|
50
|
-
- examples/
|
51
|
-
-
|
65
|
+
- examples/use_rtu_via_tcp_modbus.rb
|
66
|
+
- examples/perfomance_rtu.rb
|
52
67
|
- spec/ext_spec.rb
|
53
|
-
- spec/tcp_client_spec.rb
|
54
|
-
- spec/rtu_client_spec.rb
|
55
68
|
- spec/exception_spec.rb
|
56
|
-
- spec/client_spec.rb
|
57
69
|
- spec/tcp_server_spec.rb
|
70
|
+
- spec/logging_spec.rb
|
71
|
+
- spec/client_spec.rb
|
58
72
|
- spec/rtu_server_spec.rb
|
73
|
+
- spec/tcp_client_spec.rb
|
74
|
+
- spec/rtu_via_tcp_client_spec.rb
|
75
|
+
- spec/read_rtu_response_spec.rb
|
76
|
+
- spec/rtu_client_spec.rb
|
59
77
|
- Rakefile
|
60
78
|
- README
|
61
79
|
- AUTHORS
|
@@ -75,21 +93,27 @@ rdoc_options:
|
|
75
93
|
require_paths:
|
76
94
|
- lib
|
77
95
|
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
78
97
|
requirements:
|
79
98
|
- - ">="
|
80
99
|
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
81
103
|
version: "0"
|
82
|
-
version:
|
83
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
84
106
|
requirements:
|
85
107
|
- - ">="
|
86
108
|
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
87
112
|
version: "0"
|
88
|
-
version:
|
89
113
|
requirements: []
|
90
114
|
|
91
115
|
rubyforge_project: RModBus
|
92
|
-
rubygems_version: 1.3.
|
116
|
+
rubygems_version: 1.3.7
|
93
117
|
signing_key:
|
94
118
|
specification_version: 3
|
95
119
|
summary: RModBus - free implementation of protocol ModBus
|