rmodbus-ccutrer 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/NEWS.md +180 -0
  3. data/README.md +115 -0
  4. data/Rakefile +29 -0
  5. data/examples/perfomance_rtu.rb +56 -0
  6. data/examples/perfomance_rtu_via_tcp.rb +55 -0
  7. data/examples/perfomance_tcp.rb +55 -0
  8. data/examples/simple-xpca-gateway.rb +84 -0
  9. data/examples/use_rtu_via_tcp_modbus.rb +22 -0
  10. data/examples/use_tcp_modbus.rb +23 -0
  11. data/lib/rmodbus.rb +21 -0
  12. data/lib/rmodbus/client.rb +94 -0
  13. data/lib/rmodbus/client/slave.rb +345 -0
  14. data/lib/rmodbus/debug.rb +25 -0
  15. data/lib/rmodbus/errors.rb +42 -0
  16. data/lib/rmodbus/ext.rb +85 -0
  17. data/lib/rmodbus/options.rb +6 -0
  18. data/lib/rmodbus/proxy.rb +41 -0
  19. data/lib/rmodbus/rtu.rb +122 -0
  20. data/lib/rmodbus/rtu_client.rb +43 -0
  21. data/lib/rmodbus/rtu_server.rb +48 -0
  22. data/lib/rmodbus/rtu_slave.rb +48 -0
  23. data/lib/rmodbus/rtu_via_tcp_server.rb +35 -0
  24. data/lib/rmodbus/server.rb +246 -0
  25. data/lib/rmodbus/server/slave.rb +16 -0
  26. data/lib/rmodbus/sp.rb +36 -0
  27. data/lib/rmodbus/tcp.rb +31 -0
  28. data/lib/rmodbus/tcp_client.rb +25 -0
  29. data/lib/rmodbus/tcp_server.rb +67 -0
  30. data/lib/rmodbus/tcp_slave.rb +55 -0
  31. data/lib/rmodbus/version.rb +3 -0
  32. data/spec/client_spec.rb +88 -0
  33. data/spec/exception_spec.rb +120 -0
  34. data/spec/ext_spec.rb +52 -0
  35. data/spec/logging_spec.rb +89 -0
  36. data/spec/proxy_spec.rb +74 -0
  37. data/spec/read_rtu_response_spec.rb +92 -0
  38. data/spec/response_mismach_spec.rb +163 -0
  39. data/spec/rtu_client_spec.rb +86 -0
  40. data/spec/rtu_server_spec.rb +31 -0
  41. data/spec/rtu_via_tcp_client_spec.rb +76 -0
  42. data/spec/rtu_via_tcp_server_spec.rb +89 -0
  43. data/spec/slave_spec.rb +55 -0
  44. data/spec/spec_helper.rb +54 -0
  45. data/spec/tcp_client_spec.rb +88 -0
  46. data/spec/tcp_server_spec.rb +158 -0
  47. metadata +206 -0
@@ -0,0 +1,55 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'../lib')
2
+
3
+ require 'rmodbus'
4
+ require 'benchmark'
5
+
6
+ include ModBus
7
+
8
+ TIMES = 1000
9
+
10
+ srv = ModBus::TCPServer.new 1502
11
+ srv.coils = [0,1] * 50
12
+ srv.discrete_inputs = [1,0] * 50
13
+ srv.holding_registers = [0,1,2,3,4,5,6,7,8,9] * 10
14
+ srv.input_registers = [0,1,2,3,4,5,6,7,8,9] * 10
15
+ srv.start
16
+
17
+
18
+ cl = TCPClient.new('127.0.0.1', 1502)
19
+ cl.with_slave(1) do |slave|
20
+ Benchmark.bmbm do |x|
21
+ x.report('Read coils') do
22
+ TIMES.times { slave.read_coils 0, 100 }
23
+ end
24
+
25
+ x.report('Read discrete inputs') do
26
+ TIMES.times { slave.read_discrete_inputs 0, 100 }
27
+ end
28
+
29
+ x.report('Read holding registers') do
30
+ TIMES.times { slave.read_holding_registers 0, 100 }
31
+ end
32
+
33
+ x.report('Read input registers') do
34
+ TIMES.times { slave.read_input_registers 0, 100 }
35
+ end
36
+
37
+ x.report('Write single coil') do
38
+ TIMES.times { slave.write_single_coil 0, 1 }
39
+ end
40
+
41
+ x.report('Write single register') do
42
+ TIMES.times { slave.write_single_register 100, 0xAAAA }
43
+ end
44
+
45
+ x.report('Write multiple coils') do
46
+ TIMES.times { slave.write_multiple_coils 0, [1,0] * 50 }
47
+ end
48
+
49
+ x.report('Write multiple registers') do
50
+ TIMES.times { slave.write_multiple_registers 0, [0,1,2,3,4,5,6,7,8,9] * 10 }
51
+ end
52
+ end
53
+ end
54
+ cl.close
55
+ srv.stop
@@ -0,0 +1,84 @@
1
+ =begin
2
+ It's very simple example of implementation XPCA gateway (see http://www.xpca.org)
3
+ for communication with TCP ModBus devices
4
+ It receives REST requests (e.g http://127.0.0.1:4567/mb/127.0.0.1/8502/1/coils/6/17 )
5
+ and returns data in JSON format addr : data:
6
+ {"coils": {
7
+ "6":{
8
+ "value":0,
9
+ "timestamp":"2011-07-12 18:11:03 +0000",
10
+ "quality":"good"
11
+ },
12
+ "7":{
13
+ "value":0,
14
+ "timestamp":"2011-07-12 18:11:03 +0000",
15
+ "quality":"good"
16
+ }
17
+ ...
18
+ }
19
+
20
+ This code requies gems: rmodbus, sinatra and json
21
+ 2011 (c) Aleksey Timin
22
+ =end
23
+
24
+ require 'rubygems'
25
+ require 'rmodbus'
26
+ require 'sinatra'
27
+ require 'json'
28
+
29
+ # Launche TCP ModBus server for test
30
+ IP = '127.0.0.1'
31
+ PORT = 8502
32
+
33
+ @srv = ModBus::TCPServer.new(PORT,1)
34
+
35
+ @srv.holding_registers = Array.new(100) { |i| i = i + 1 }
36
+ @srv.input_registers = Array.new(100) { |i| i = i + 1 }
37
+ @srv.coils = Array.new(100) { |i| i = 0 }
38
+ @srv.discrete_inputs = Array.new(100) { |i| i = 0 }
39
+
40
+ @srv.start
41
+
42
+
43
+ # Calc a GET request
44
+ # @example
45
+ # http://127.0.0.1:4567/mb/127.0.0.1/8502/1/coils/6/17
46
+ #
47
+ # HTTP route: GET http://localhost/mb/:ip/:port/:slave/:dataplace/:firstaddr/:lastaddr
48
+ #
49
+ # :ip - ip addr of ModBus TCP Server
50
+ # :port - port of ModBUs TCP Server
51
+ # :slave - id of slave device
52
+ # :dataplace - valid values: coils, discrete_inputs, input_registers, holding_registers
53
+ # :firstaddr - first addr of registers(contacts)
54
+ # :lastaddr - last addr of registers(contacts)
55
+ get '/mb/:ip/:port/:slave/:dataplace/:firstaddr/:lastaddr' do
56
+ resp = {}
57
+ begin
58
+ data = []
59
+ ModBus::TCPClient.new(params[:ip].to_s, params[:port].to_i) do |cl|
60
+ cl.with_slave(params[:slave].to_i) do |slave|
61
+ slave.debug = true
62
+ dataplace = slave.send params[:dataplace]
63
+ data = dataplace[params[:firstaddr].to_i .. params[:lastaddr].to_i]
64
+ end
65
+ end
66
+
67
+ resp = { params[:dataplace] => {}}
68
+ data.each_with_index do |v,i|
69
+ resp[params[:dataplace]][params[:firstaddr].to_i + i] = {
70
+ :value => v,
71
+ :timestamp => Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z"),
72
+ :quality => "good"
73
+ }
74
+ end
75
+ rescue Exception => e
76
+ resp = { :error => {
77
+ :type => e.class,
78
+ :message => e.message }
79
+ }
80
+ end
81
+
82
+ content_type "application/json"
83
+ resp.to_json
84
+ end
@@ -0,0 +1,22 @@
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::RTUClient.connect('127.0.0.1', 10002) do |cl|
13
+ cl.with_slave(1) do |slave|
14
+ slave.debug = true
15
+ regs = slave.holding_registers
16
+ puts regs[0..3]
17
+ regs[0..3] = [2,0,1,1]
18
+ puts regs[0..3]
19
+ end
20
+ end
21
+
22
+ srv.shutdown
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib/')
2
+ require 'rmodbus'
3
+
4
+ srv = ModBus::TCPServer.new(8502,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.audit = true
11
+ srv.start
12
+
13
+ ModBus::TCPClient.connect('127.0.0.1', 8502) do |cl|
14
+ cl.with_slave(1) do |slave|
15
+ slave.debug = true
16
+ regs = slave.holding_registers
17
+ puts regs[0..3]
18
+ regs[0..3] = [2,0,1,1]
19
+ puts regs[0..3]
20
+ end
21
+ end
22
+
23
+ srv.stop
data/lib/rmodbus.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'rmodbus/ext'
2
+ require 'rmodbus/proxy'
3
+ require 'rmodbus/version'
4
+
5
+ module ModBus
6
+ autoload :Errors, 'rmodbus/errors'
7
+ autoload :Debug, 'rmodbus/debug'
8
+ autoload :Options, 'rmodbus/options'
9
+ autoload :SP, 'rmodbus/sp'
10
+ autoload :RTU, 'rmodbus/rtu'
11
+ autoload :TCP, 'rmodbus/tcp'
12
+ autoload :Client, 'rmodbus/client'
13
+ autoload :Server, 'rmodbus/server'
14
+ autoload :TCPSlave, 'rmodbus/tcp_slave'
15
+ autoload :TCPClient, 'rmodbus/tcp_client'
16
+ autoload :TCPServer, 'rmodbus/tcp_server'
17
+ autoload :RTUSlave, 'rmodbus/rtu_slave'
18
+ autoload :RTUClient, 'rmodbus/rtu_client'
19
+ autoload :RTUServer, 'rmodbus/rtu_server'
20
+ autoload :RTUViaTCPServer, 'rmodbus/rtu_via_tcp_server'
21
+ end
@@ -0,0 +1,94 @@
1
+ module ModBus
2
+ # @abstract
3
+ class Client
4
+ autoload :Slave, 'rmodbus/client/slave'
5
+
6
+ include Errors
7
+ include Debug
8
+ include Options
9
+
10
+ # Initialized client (alias :connect)
11
+ # @example
12
+ # Client.new(any_args) do |client|
13
+ # client.closed? #=> false
14
+ # end
15
+ # @param *args depends on implementation
16
+ # @yield return client object and close it before exit
17
+ # @return [Client] client object
18
+ def initialize(*args, &block)
19
+ # Defaults
20
+ @debug = false
21
+ @raise_exception_on_mismatch = false
22
+ @read_retry_timeout = 1
23
+ @read_retries = 1
24
+
25
+ @io = open_connection(*args)
26
+ if block_given?
27
+ begin
28
+ yield self
29
+ ensure
30
+ close
31
+ end
32
+ else
33
+ self
34
+ end
35
+ end
36
+
37
+ class << self
38
+ alias_method :connect, :new
39
+ end
40
+
41
+ # Given slave object
42
+ # @example
43
+ # cl = Client.new
44
+ # cl.with_slave(1) do |slave|
45
+ # slave.holding_registers[0..100]
46
+ # end
47
+ #
48
+ # @param [Integer, #read] uid slave devise
49
+ # @return [Slave] slave object
50
+ def with_slave(uid, &block)
51
+ slave = get_slave(uid, @io)
52
+ slave.debug = debug
53
+ slave.raise_exception_on_mismatch = raise_exception_on_mismatch
54
+ slave.read_retries = read_retries
55
+ slave.read_retry_timeout = read_retry_timeout
56
+ if block_given?
57
+ yield slave
58
+ else
59
+ slave
60
+ end
61
+ end
62
+
63
+ # Check connections
64
+ # @return [Boolean]
65
+ def closed?
66
+ @io.closed?
67
+ end
68
+
69
+ # Close connections
70
+ def close
71
+ @io.close unless @io.closed?
72
+ end
73
+
74
+ protected
75
+ def open_connection(*args)
76
+ #Stub conn object
77
+ @io = Object.new
78
+
79
+ @io.instance_eval """
80
+ def close
81
+ end
82
+
83
+ def closed?
84
+ true
85
+ end
86
+ """
87
+ @io
88
+ end
89
+
90
+ def get_slave(uid,io)
91
+ Slave.new(uid, io)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,345 @@
1
+ require 'timeout'
2
+
3
+ module ModBus
4
+ class Client
5
+ class Slave
6
+ include Errors
7
+ include Debug
8
+ include Options
9
+ # Number of times to retry on read and read timeouts
10
+ attr_accessor :uid
11
+ Exceptions = {
12
+ 1 => IllegalFunction.new("The function code received in the query is not an allowable action for the server"),
13
+ 2 => IllegalDataAddress.new("The data address received in the query is not an allowable address for the server"),
14
+ 3 => IllegalDataValue.new("A value contained in the query data field is not an allowable value for server"),
15
+ 4 => SlaveDeviceFailure.new("An unrecoverable error occurred while the server was attempting to perform the requested action"),
16
+ 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"),
17
+ 6 => SlaveDeviceBus.new("The server is engaged in processing a long duration program command"),
18
+ 8 => MemoryParityError.new("The extended file area failed to pass a consistency check")
19
+ }
20
+ def initialize(uid, io)
21
+ @uid = uid
22
+ @io = io
23
+ end
24
+
25
+ # Returns a ModBus::ReadWriteProxy hash interface for coils
26
+ #
27
+ # @example
28
+ # coils[addr] => [1]
29
+ # coils[addr1..addr2] => [1, 0, ..]
30
+ # coils[addr] = 0 => [0]
31
+ # coils[addr1..addr2] = [1, 0, ..] => [1, 0, ..]
32
+ #
33
+ # @return [ReadWriteProxy] proxy object
34
+ def coils
35
+ ModBus::ReadWriteProxy.new(self, :coil)
36
+ end
37
+
38
+ # Read coils
39
+ #
40
+ # @example
41
+ # read_coils(addr, ncoils) => [1, 0, ..]
42
+ #
43
+ # @param [Integer] addr address first coil
44
+ # @param [Integer] ncoils number coils
45
+ # @return [Array] coils
46
+ def read_coils(addr, ncoils = 1)
47
+ query("\x1" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
48
+ end
49
+
50
+ def read_coil(addr)
51
+ read_coils(addr, 1).first
52
+ end
53
+
54
+ # Write a single coil
55
+ #
56
+ # @example
57
+ # write_single_coil(1, 0) => self
58
+ #
59
+ # @param [Integer] addr address coil
60
+ # @param [Integer] val value coil (0 or other)
61
+ # @return self
62
+ def write_single_coil(addr, val)
63
+ if val == 0
64
+ query("\x5" + addr.to_word + 0.to_word)
65
+ else
66
+ query("\x5" + addr.to_word + 0xff00.to_word)
67
+ end
68
+ self
69
+ end
70
+ alias_method :write_coil, :write_single_coil
71
+
72
+ # Write multiple coils
73
+ #
74
+ # @example
75
+ # write_multiple_coils(1, [0,1,0,1]) => self
76
+ #
77
+ # @param [Integer] addr address first coil
78
+ # @param [Array] vals written coils
79
+ def write_multiple_coils(addr, vals)
80
+ nbyte = ((vals.size-1) >> 3) + 1
81
+ sum = 0
82
+ (vals.size - 1).downto(0) do |i|
83
+ sum = sum << 1
84
+ sum |= 1 if vals[i] > 0
85
+ end
86
+
87
+ s_val = ""
88
+ nbyte.times do
89
+ s_val << (sum & 0xff).chr
90
+ sum >>= 8
91
+ end
92
+
93
+ query("\xf" + addr.to_word + vals.size.to_word + nbyte.chr + s_val)
94
+ self
95
+ end
96
+ alias_method :write_coils, :write_multiple_coils
97
+
98
+ # Returns a ModBus::ReadOnlyProxy hash interface for discrete inputs
99
+ #
100
+ # @example
101
+ # discrete_inputs[addr] => [1]
102
+ # discrete_inputs[addr1..addr2] => [1, 0, ..]
103
+ #
104
+ # @return [ReadOnlyProxy] proxy object
105
+ def discrete_inputs
106
+ ModBus::ReadOnlyProxy.new(self, :discrete_input)
107
+ end
108
+
109
+ # Read discrete inputs
110
+ #
111
+ # @example
112
+ # read_discrete_inputs(addr, ninputs) => [1, 0, ..]
113
+ #
114
+ # @param [Integer] addr address first input
115
+ # @param[Integer] ninputs number inputs
116
+ # @return [Array] inputs
117
+ def read_discrete_inputs(addr, ninputs = 1)
118
+ query("\x2" + addr.to_word + ninputs.to_word).unpack_bits[0..ninputs-1]
119
+ end
120
+
121
+ def read_discrete_input(addr)
122
+ read_discrete_inputs(addr, 1).first
123
+ end
124
+
125
+ # Returns a read/write ModBus::ReadOnlyProxy hash interface for coils
126
+ #
127
+ # @example
128
+ # input_registers[addr] => [1]
129
+ # input_registers[addr1..addr2] => [1, 0, ..]
130
+ #
131
+ # @return [ReadOnlyProxy] proxy object
132
+ def input_registers
133
+ ModBus::ReadOnlyProxy.new(self, :input_register)
134
+ end
135
+
136
+ # Read input registers
137
+ #
138
+ # @example
139
+ # read_input_registers(1, 5) => [1, 0, ..]
140
+ #
141
+ # @param [Integer] addr address first registers
142
+ # @param [Integer] nregs number registers
143
+ # @return [Array] registers
144
+ def read_input_registers(addr, nregs = 1)
145
+ query("\x4" + addr.to_word + nregs.to_word).unpack('n*')
146
+ end
147
+
148
+ def read_input_register(addr)
149
+ read_input_registers(addr, 1).first
150
+ end
151
+
152
+ # Returns a ModBus::ReadWriteProxy hash interface for holding registers
153
+ #
154
+ # @example
155
+ # holding_registers[addr] => [123]
156
+ # holding_registers[addr1..addr2] => [123, 234, ..]
157
+ # holding_registers[addr] = 123 => 123
158
+ # holding_registers[addr1..addr2] = [234, 345, ..] => [234, 345, ..]
159
+ #
160
+ # @return [ReadWriteProxy] proxy object
161
+ def holding_registers
162
+ ModBus::ReadWriteProxy.new(self, :holding_register)
163
+ end
164
+
165
+ # Read holding registers
166
+ #
167
+ # @example
168
+ # read_holding_registers(1, 5) => [1, 0, ..]
169
+ #
170
+ # @param [Integer] addr address first registers
171
+ # @param [Integer] nregs number registers
172
+ # @return [Array] registers
173
+ def read_holding_registers(addr, nregs = 1)
174
+ query("\x3" + addr.to_word + nregs.to_word).unpack('n*')
175
+ end
176
+
177
+ def read_holding_register(addr)
178
+ read_holding_registers(addr, 1).first
179
+ end
180
+
181
+ # Write a single holding register
182
+ #
183
+ # @example
184
+ # write_single_register(1, 0xaa) => self
185
+ #
186
+ # @param [Integer] addr address registers
187
+ # @param [Integer] val written to register
188
+ # @return self
189
+ def write_single_register(addr, val)
190
+ query("\x6" + addr.to_word + val.to_word)
191
+ self
192
+ end
193
+ alias_method :write_holding_register, :write_single_register
194
+
195
+
196
+ # Write multiple holding registers
197
+ #
198
+ # @example
199
+ # write_multiple_registers(1, [0xaa, 0]) => self
200
+ #
201
+ # @param [Integer] addr address first registers
202
+ # @param [Array] val written registers
203
+ # @return self
204
+ def write_multiple_registers(addr, vals)
205
+ s_val = ""
206
+ vals.each do |reg|
207
+ s_val << reg.to_word
208
+ end
209
+
210
+ query("\x10" + addr.to_word + vals.size.to_word + (vals.size * 2).chr + s_val)
211
+ self
212
+ end
213
+ alias_method :write_holding_registers, :write_multiple_registers
214
+
215
+ # Mask a holding register
216
+ #
217
+ # @example
218
+ # mask_write_register(1, 0xAAAA, 0x00FF) => self
219
+ # @param [Integer] addr address registers
220
+ # @param [Integer] and_mask mask for AND operation
221
+ # @param [Integer] or_mask mask for OR operation
222
+ def mask_write_register(addr, and_mask, or_mask)
223
+ query("\x16" + addr.to_word + and_mask.to_word + or_mask.to_word)
224
+ self
225
+ end
226
+
227
+ # Read/write multiple holding registers
228
+ #
229
+ # @example
230
+ # read_write_multiple_registers(1, 5, 1, [0xaa, 0]) => [1, 0, ..]
231
+ #
232
+ # @param [Integer] addr_r address first registers to read
233
+ # @param [Integer] nregs number registers to read
234
+ # @param [Integer] addr_w address first registers to write
235
+ # @param [Array] vals written registers
236
+ # @return [Array] registers
237
+ def read_write_multiple_registers(addr_r, nregs, addr_w, vals)
238
+ s_val = ""
239
+ vals.each do |reg|
240
+ s_val << reg.to_word
241
+ end
242
+
243
+ query("\x17" + addr_r.to_word + nregs.to_word +
244
+ addr_w.to_word + vals.size.to_word + (vals.size * 2).chr + s_val).unpack('n*')
245
+ end
246
+ alias_method :read_write_holding_registers, :read_write_multiple_registers
247
+
248
+ # Request pdu to slave device
249
+ #
250
+ # @param [String] pdu request to slave
251
+ # @return [String] received data
252
+ #
253
+ # @raise [ResponseMismatch] the received echo response differs from the request
254
+ # @raise [ModBusTimeout] timed out during read attempt
255
+ # @raise [ModBusException] unknown error
256
+ # @raise [IllegalFunction] function code received in the query is not an allowable action for the server
257
+ # @raise [IllegalDataAddress] data address received in the query is not an allowable address for the server
258
+ # @raise [IllegalDataValue] value contained in the query data field is not an allowable value for server
259
+ # @raise [SlaveDeviceFailure] unrecoverable error occurred while the server was attempting to perform the requested action
260
+ # @raise [Acknowledge] server has accepted the request and is processing it, but a long duration of time will be required to do so
261
+ # @raise [SlaveDeviceBus] server is engaged in processing a long duration program command
262
+ # @raise [MemoryParityError] extended file area failed to pass a consistency check
263
+ def query(request)
264
+ tried = 0
265
+ response = ""
266
+ begin
267
+ ::Timeout.timeout(@read_retry_timeout, ModBusTimeout) do
268
+ send_pdu(request)
269
+ response = read_pdu unless uid == 0
270
+ end
271
+ rescue ModBusTimeout => err
272
+ log "Timeout of read operation: (#{@read_retries - tried})"
273
+ tried += 1
274
+ retry unless tried >= @read_retries
275
+ raise ModBusTimeout.new, "Timed out during read attempt"
276
+ end
277
+
278
+ return nil if response.size == 0
279
+
280
+ read_func = response.getbyte(0)
281
+ if read_func >= 0x80
282
+ exc_id = response.getbyte(1)
283
+ raise Exceptions[exc_id] unless Exceptions[exc_id].nil?
284
+
285
+ raise ModBusException.new, "Unknown error"
286
+ end
287
+
288
+ check_response_mismatch(request, response) if raise_exception_on_mismatch
289
+ response[2..-1]
290
+ end
291
+
292
+ private
293
+ def check_response_mismatch(request, response)
294
+ read_func = response.getbyte(0)
295
+ data = response[2..-1]
296
+ #Mismatch functional code
297
+ send_func = request.getbyte(0)
298
+ if read_func != send_func
299
+ msg = "Function code is mismatch (expected #{send_func}, got #{read_func})"
300
+ end
301
+
302
+ case read_func
303
+ when 1,2
304
+ bc = request.getword(3)/8 + 1
305
+ if data.size != bc
306
+ msg = "Byte count is mismatch (expected #{bc}, got #{data.size} bytes)"
307
+ end
308
+ when 3,4
309
+ rc = request.getword(3)
310
+ if data.size/2 != rc
311
+ msg = "Register count is mismatch (expected #{rc}, got #{data.size/2} regs)"
312
+ end
313
+ when 5,6
314
+ exp_addr = request.getword(1)
315
+ got_addr = response.getword(1)
316
+ if exp_addr != got_addr
317
+ msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
318
+ end
319
+
320
+ exp_val = request.getword(3)
321
+ got_val = response.getword(3)
322
+ if exp_val != got_val
323
+ msg = "Value is mismatch (expected 0x#{exp_val.to_s(16)}, got 0x#{got_val.to_s(16)})"
324
+ end
325
+ when 15,16
326
+ exp_addr = request.getword(1)
327
+ got_addr = response.getword(1)
328
+ if exp_addr != got_addr
329
+ msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
330
+ end
331
+
332
+ exp_quant = request.getword(3)
333
+ got_quant = response.getword(3)
334
+ if exp_quant != got_quant
335
+ msg = "Quantity is mismatch (expected #{exp_quant}, got #{got_quant})"
336
+ end
337
+ else
338
+ warn "Fuiction (#{read_func}) is not supported raising response mismatch"
339
+ end
340
+
341
+ raise ResponseMismatch.new(msg, request, response) if msg
342
+ end
343
+ end
344
+ end
345
+ end