rmodbus 0.4.0 → 0.5.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.
- 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
|