rmodbus 1.3.3 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/NEWS.md +19 -0
  3. data/README.md +8 -8
  4. data/examples/perfomance_rtu.rb +55 -56
  5. data/examples/perfomance_rtu_via_tcp.rb +54 -55
  6. data/examples/perfomance_tcp.rb +54 -55
  7. data/examples/simple_xpca_gateway.rb +85 -0
  8. data/examples/use_rtu_via_tcp_modbus.rb +14 -11
  9. data/examples/use_tcp_modbus.rb +14 -11
  10. data/lib/rmodbus/client/slave.rb +333 -0
  11. data/lib/rmodbus/client.rb +15 -10
  12. data/lib/rmodbus/debug.rb +12 -15
  13. data/lib/rmodbus/errors.rb +26 -2
  14. data/lib/rmodbus/ext.rb +72 -51
  15. data/lib/rmodbus/options.rb +4 -1
  16. data/lib/rmodbus/proxy.rb +14 -9
  17. data/lib/rmodbus/rtu.rb +89 -125
  18. data/lib/rmodbus/rtu_client.rb +22 -2
  19. data/lib/rmodbus/rtu_server.rb +16 -12
  20. data/lib/rmodbus/rtu_slave.rb +26 -3
  21. data/lib/rmodbus/rtu_via_tcp_server.rb +12 -19
  22. data/lib/rmodbus/server/slave.rb +18 -0
  23. data/lib/rmodbus/server.rb +227 -84
  24. data/lib/rmodbus/sp.rb +10 -12
  25. data/lib/rmodbus/tcp.rb +9 -10
  26. data/lib/rmodbus/tcp_client.rb +3 -0
  27. data/lib/rmodbus/tcp_server.rb +41 -35
  28. data/lib/rmodbus/tcp_slave.rb +19 -18
  29. data/lib/rmodbus/version.rb +3 -2
  30. data/lib/rmodbus.rb +20 -21
  31. metadata +63 -50
  32. data/Rakefile +0 -29
  33. data/examples/simple-xpca-gateway.rb +0 -84
  34. data/lib/rmodbus/rtu_via_tcp_client.rb +0 -26
  35. data/lib/rmodbus/rtu_via_tcp_slave.rb +0 -29
  36. data/lib/rmodbus/slave.rb +0 -310
  37. data/spec/client_spec.rb +0 -88
  38. data/spec/exception_spec.rb +0 -119
  39. data/spec/ext_spec.rb +0 -52
  40. data/spec/logging_spec.rb +0 -89
  41. data/spec/proxy_spec.rb +0 -74
  42. data/spec/read_rtu_response_spec.rb +0 -92
  43. data/spec/response_mismach_spec.rb +0 -163
  44. data/spec/rtu_client_spec.rb +0 -86
  45. data/spec/rtu_server_spec.rb +0 -30
  46. data/spec/rtu_via_tcp_client_spec.rb +0 -76
  47. data/spec/rtu_via_tcp_server_spec.rb +0 -16
  48. data/spec/slave_spec.rb +0 -55
  49. data/spec/spec_helper.rb +0 -54
  50. data/spec/tcp_client_spec.rb +0 -88
  51. data/spec/tcp_server_spec.rb +0 -129
@@ -1,127 +1,270 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
4
  # Module for implementation ModBus server
3
5
  module Server
4
- Funcs = [1,2,3,4,5,6,15,16]
6
+ autoload :Slave, "rmodbus/server/slave"
7
+
8
+ attr_accessor :promiscuous, :request_callback, :response_callback
5
9
 
6
- attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers, :uid
7
- @coils = []
8
- @discrete_inputs = []
9
- @holding_registers =[]
10
- @input_registers = []
10
+ FUNCS = [1, 2, 3, 4, 5, 6, 15, 16, 22, 23].freeze
11
+
12
+ def with_slave(uid)
13
+ slave = slaves[uid] ||= Server::Slave.new
14
+ if block_given?
15
+ yield slave
16
+ else
17
+ slave
18
+ end
19
+ end
11
20
 
12
21
  private
13
22
 
14
- def exec_req(req, coils, discrete_inputs, holding_registers, input_registers)
15
- func = req.getbyte(0)
23
+ def slaves
24
+ @slaves ||= {}
25
+ end
26
+
27
+ def exec_req(uid, func, params, pdu, is_response: false)
28
+ if is_response
29
+ log("Server RX response #{func & 0x7f} from #{uid}: #{params.inspect}")
30
+ else
31
+ log("Server RX function #{func} to #{uid}: #{params.inspect}")
32
+ end
33
+ request_callback&.call(uid, func, params) unless is_response
34
+
35
+ if uid.zero?
36
+ slaves.each_key { |specific_uid| exec_req(specific_uid, func, params, pdu) }
37
+ return
38
+ end
39
+ slave = slaves[uid]
40
+ return nil if !slave && !promiscuous
41
+
42
+ if promiscuous && !slave && is_response
43
+ # we saw a request to a slave that we don't own; try
44
+ # and parse this as a response, not a request
45
+
46
+ response_callback&.call(uid, func, params, @pending_response_req)
47
+ @pending_response_req = nil
48
+ return
49
+ end
50
+
51
+ unless FUNCS.include?(func)
52
+ log("Server RX unrecognized function #{func} to #{uid}")
53
+ return unless slave
16
54
 
17
- unless Funcs.include?(func)
18
- params = { :err => 1 }
55
+ return (func | 0x80).chr + 1.chr
19
56
  end
20
57
 
58
+ # keep track of the request so that promiscuous printing of the response can have context if necessary
59
+ @pending_response_req = params
60
+
61
+ return unless slave
62
+
63
+ pdu = process_func(func, slave, pdu, params)
64
+ if response_callback
65
+ res = parse_response(pdu.getbyte(0), pdu)
66
+ response_callback.call(uid, pdu.getbyte(0), res, params)
67
+ end
68
+ pdu
69
+ end
70
+
71
+ def parse_request(func, req)
21
72
  case func
22
- when 1
23
- params = parse_read_func(req, coils, 2000)
24
- if params[:err] == 0
25
- val = coils[params[:addr],params[:quant]].pack_to_word
26
- pdu = func.chr + val.size.chr + val
27
- end
28
- when 2
29
- params = parse_read_func(req, discrete_inputs, 2000)
30
- if params[:err] == 0
31
- val = discrete_inputs[params[:addr],params[:quant]].pack_to_word
32
- pdu = func.chr + val.size.chr + val
33
- end
34
- when 3
35
- params = parse_read_func(req, holding_registers)
36
- if params[:err] == 0
37
- pdu = func.chr + (params[:quant] * 2).chr + holding_registers[params[:addr],params[:quant]].pack('n*')
38
- end
39
- when 4
40
- params = parse_read_func(req, input_registers)
41
- if params[:err] == 0
42
- pdu = func.chr + (params[:quant] * 2).chr + input_registers[params[:addr],params[:quant]].pack('n*')
43
- end
44
- when 5
45
- params = parse_write_coil_func(req)
46
- if params[:err] == 0
47
- coils[params[:addr]] = params[:val]
48
- pdu = req
49
- end
50
- when 6
51
- params = parse_write_register_func(req)
52
- if params[:err] == 0
53
- holding_registers[params[:addr]] = params[:val]
54
- pdu = req
55
- end
56
- when 15
57
- params = parse_write_multiple_coils_func(req)
58
- if params[:err] == 0
59
- coils[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
60
- pdu = req[0,5]
61
- end
62
- when 16
63
- params = parse_write_multiple_registers_func(req)
64
- if params[:err] == 0
65
- holding_registers[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
66
- pdu = req[0,5]
67
- end
73
+ when 1, 2, 3, 4
74
+ parse_read_func(req)
75
+ when 5
76
+ parse_write_coil_func(req)
77
+ when 6
78
+ parse_write_register_func(req)
79
+ when 15
80
+ parse_write_multiple_coils_func(req)
81
+ when 16
82
+ parse_write_multiple_registers_func(req)
83
+ when 22
84
+ parse_mask_write_register_func(req)
85
+ when 23
86
+ parse_read_write_multiple_registers_func(req)
68
87
  end
88
+ end
69
89
 
70
- if params[:err] == 0
71
- pdu
90
+ def parse_response(func, res)
91
+ if func & 0x80 == 0x80 && FUNCS.include?(func & 0x7f)
92
+ return nil unless res.length == 2
93
+
94
+ return { err: res[1].ord }
95
+ end
96
+
97
+ case func
98
+ when 1, 2
99
+ return nil unless res.length == res[1].ord + 2
100
+
101
+ res[2..].unpack_bits
102
+ when 3, 4, 23
103
+ return nil unless res.length == res[1].ord + 2
104
+
105
+ res[2..].unpack("n*")
106
+ when 5, 6, 15, 16
107
+ return nil unless res.length == 5
108
+
109
+ {}
110
+ when 22
111
+ return nil unless res.length == 7
112
+
113
+ {}
114
+ end
115
+ end
116
+
117
+ def process_func(func, slave, req, params)
118
+ case func
119
+ when 1
120
+ unless (err = validate_read_func(params, slave.coils, 2000))
121
+ val = slave.coils[params[:addr], params[:quant]].pack_bits
122
+ pdu = func.chr + val.size.chr + val
123
+ end
124
+ when 2
125
+ unless (err = validate_read_func(params, slave.discrete_inputs, 2000))
126
+ val = slave.discrete_inputs[params[:addr], params[:quant]].pack_bits
127
+ pdu = func.chr + val.size.chr + val
128
+ end
129
+ when 3
130
+ unless (err = validate_read_func(params, slave.holding_registers))
131
+ pdu = func.chr + (params[:quant] * 2).chr + slave.holding_registers[params[:addr],
132
+ params[:quant]].pack("n*")
133
+ end
134
+ when 4
135
+ unless (err = validate_read_func(params, slave.input_registers))
136
+ pdu = func.chr + (params[:quant] * 2).chr + slave.input_registers[params[:addr], params[:quant]].pack("n*")
137
+ end
138
+ when 5
139
+ unless (err = validate_write_coil_func(params, slave))
140
+ params[:val] = 1 if params[:val] == 0xff00
141
+ slave.coils[params[:addr]] = params[:val]
142
+ pdu = req
143
+ end
144
+ when 6
145
+ unless (err = validate_write_register_func(params, slave))
146
+ slave.holding_registers[params[:addr]] = params[:val]
147
+ pdu = req
148
+ end
149
+ when 15
150
+ unless (err = validate_write_multiple_coils_func(params, slave))
151
+ slave.coils[params[:addr], params[:quant]] = params[:val][0, params[:quant]]
152
+ pdu = req[0, 5]
153
+ end
154
+ when 16
155
+ unless (err = validate_write_multiple_registers_func(params, slave))
156
+ slave.holding_registers[params[:addr], params[:quant]] = params[:val]
157
+ pdu = req[0, 5]
158
+ end
159
+ when 22
160
+ unless (err = validate_write_register_func(params, slave))
161
+ addr = params[:addr]
162
+ and_mask = params[:and_mask]
163
+ slave.holding_registers[addr] = (slave.holding_registers[addr] & and_mask) | (params[:or_mask] & ~and_mask)
164
+ pdu = req
165
+ end
166
+ when 23
167
+ unless (err = validate_read_write_multiple_registers_func(params, slave))
168
+ slave.holding_registers[params[:write][:addr], params[:write][:quant]] = params[:write][:val]
169
+ pdu = func.chr + (params[:read][:quant] * 2).chr + slave.holding_registers[params[:read][:addr],
170
+ params[:read][:quant]].pack("n*")
171
+ end
172
+ end
173
+
174
+ if err
175
+ (func | 0x80).chr + err.chr
72
176
  else
73
- pdu = (func | 0x80).chr + params[:err].chr
177
+ pdu
74
178
  end
75
179
  end
76
180
 
77
- def parse_read_func(req, field, quant_max=0x7d)
78
- quant = req[3,2].unpack('n')[0]
181
+ def parse_read_func(req, expected_length = 5)
182
+ return nil if expected_length && req.length != expected_length
79
183
 
80
- return { :err => 3} unless quant <= quant_max
184
+ { quant: req[3, 2].unpack1("n"), addr: req[1, 2].unpack1("n") }
185
+ end
81
186
 
82
- addr = req[1,2].unpack('n')[0]
83
- return { :err => 2 } unless addr + quant <= field.size
187
+ def validate_read_func(params, field, quant_max = 0x7d)
188
+ return 3 unless params[:quant] <= quant_max
84
189
 
85
- return { :err => 0, :quant => quant, :addr => addr }
190
+ 2 unless params[:addr] + params[:quant] <= field.size
86
191
  end
87
192
 
88
193
  def parse_write_coil_func(req)
89
- addr = req[1,2].unpack('n')[0]
90
- return { :err => 2 } unless addr <= @coils.size
194
+ return nil unless req.length == 5
91
195
 
92
- val = req[3,2].unpack('n')[0]
93
- return { :err => 3 } unless val == 0 or val == 0xff00
196
+ { addr: req[1, 2].unpack1("n"), val: req[3, 2].unpack1("n") }
197
+ end
198
+
199
+ def validate_write_coil_func(params, slave)
200
+ return 2 unless params[:addr] <= slave.coils.size
94
201
 
95
- val = 1 if val == 0xff00
96
- return { :err => 0, :addr => addr, :val => val }
202
+ 3 unless params[:val].zero? || (params[:val] == 0xff00)
97
203
  end
98
204
 
99
205
  def parse_write_register_func(req)
100
- addr = req[1,2].unpack('n')[0]
101
- return { :err => 2 } unless addr <= @holding_registers.size
206
+ return nil unless req.length == 5
102
207
 
103
- val = req[3,2].unpack('n')[0]
208
+ { addr: req[1, 2].unpack1("n"), val: req[3, 2].unpack1("n") }
209
+ end
104
210
 
105
- return { :err => 0, :addr => addr, :val => val }
106
- end
211
+ def validate_write_register_func(params, slave)
212
+ 2 unless params[:addr] <= slave.holding_registers.size
213
+ end
107
214
 
108
215
  def parse_write_multiple_coils_func(req)
109
- params = parse_read_func(req, @coils)
216
+ return nil if req.length < 7
110
217
 
111
- if params[:err] == 0
112
- params = {:err => 0, :addr => params[:addr], :quant => params[:quant], :val => req[6,params[:quant]].unpack_bits }
113
- end
218
+ params = parse_read_func(req, nil)
219
+ return nil if req.length != 6 + ((params[:quant] + 7) / 8)
220
+
221
+ params[:val] = req[6, params[:quant]].unpack_bits
114
222
  params
115
223
  end
116
224
 
225
+ def validate_write_multiple_coils_func(params, slave)
226
+ validate_read_func(params, slave.coils)
227
+ end
228
+
117
229
  def parse_write_multiple_registers_func(req)
118
- params = parse_read_func(req, @holding_registers)
230
+ return nil if req.length < 8
119
231
 
120
- if params[:err] == 0
121
- params = {:err => 0, :addr => params[:addr], :quant => params[:quant], :val => req[6,params[:quant] * 2].unpack('n*')}
122
- end
232
+ params = parse_read_func(req, nil)
233
+ return nil if req.length != 6 + (params[:quant] * 2)
234
+
235
+ params[:val] = req[6, params[:quant] * 2].unpack("n*")
123
236
  params
124
237
  end
125
238
 
239
+ def validate_write_multiple_registers_func(params, slave)
240
+ validate_read_func(params, slave.holding_registers)
241
+ end
242
+
243
+ def parse_mask_write_register_func(req)
244
+ return nil if req.length != 7
245
+
246
+ {
247
+ addr: req[1, 2].unpack1("n"),
248
+ and_mask: req[3, 2].unpack1("n"),
249
+ or_mask: req[5, 2].unpack1("n")
250
+ }
251
+ end
252
+
253
+ def parse_read_write_multiple_registers_func(req)
254
+ return nil if req.length < 12
255
+
256
+ params = { read: parse_read_func(req, nil),
257
+ write: parse_write_multiple_registers_func(req[4..]) }
258
+ return nil if params[:write].nil?
259
+
260
+ params
261
+ end
262
+
263
+ def validate_read_write_multiple_registers_func(params, slave)
264
+ result = validate_read_func(params[:read], slave.holding_registers)
265
+ return result if result
266
+
267
+ validate_write_multiple_registers_func(params[:write], slave)
268
+ end
126
269
  end
127
270
  end
data/lib/rmodbus/sp.rb CHANGED
@@ -1,36 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
- require 'serialport'
3
- rescue Exception => e
4
- warn "[WARNING] Install `serialport` gem for use RTU protocols"
4
+ require "ccutrer-serialport"
5
+ rescue LoadError
6
+ warn "[WARNING] Install `ccutrer-serialport` gem for use RTU protocols"
5
7
  end
6
8
 
7
9
  module ModBus
8
10
  module SP
9
11
  attr_reader :port, :baud, :data_bits, :stop_bits, :parity, :read_timeout
12
+
10
13
  # Open serial port
11
- # @param [String] port name serial ports ("/dev/ttyS0" POSIX, "com1" - Windows)
14
+ # @param [String] port name serial ports ("/dev/ttyS0")
12
15
  # @param [Integer] baud rate serial port (default 9600)
13
16
  # @param [Hash] opts the options of serial port
14
17
  #
15
18
  # @option opts [Integer] :data_bits from 5 to 8
16
19
  # @option opts [Integer] :stop_bits 1 or 2
17
- # @option opts [Integer] :parity NONE, EVEN or ODD
18
- # @option opts [Integer] :read_timeout default 100 ms
20
+ # @option opts [Integer] :parity :none, :even or :odd
19
21
  # @return [SerialPort] io serial port
20
22
  def open_serial_port(port, baud, opts = {})
21
23
  @port, @baud = port, baud
22
24
 
23
- @data_bits, @stop_bits, @parity, @read_timeout = 8, 1, SerialPort::NONE, 100
25
+ @data_bits, @stop_bits, @parity = 8, 1, :none
24
26
 
25
27
  @data_bits = opts[:data_bits] unless opts[:data_bits].nil?
26
28
  @stop_bits = opts[:stop_bits] unless opts[:stop_bits].nil?
27
29
  @parity = opts[:parity] unless opts[:parity].nil?
28
- @read_timeout = opts[:read_timeout] unless opts[:read_timeout].nil?
29
30
 
30
- io = SerialPort.new(@port, @baud, @data_bits, @stop_bits, @parity)
31
- io.flow_control = SerialPort::NONE
32
- io.read_timeout = @read_timeout
33
- io
31
+ CCutrer::SerialPort.new(@port, baud: @baud, data_bits: @data_bits, stop_bits: @stop_bits, parity: @parity)
34
32
  end
35
33
  end
36
34
  end
data/lib/rmodbus/tcp.rb CHANGED
@@ -1,32 +1,31 @@
1
- require 'socket'
2
- require 'timeout'
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
3
4
 
4
5
  module ModBus
5
6
  module TCP
6
7
  include Errors
7
- include Timeout
8
8
  attr_reader :ipaddr, :port
9
+
9
10
  # Open TCP socket
10
11
  #
11
12
  # @param [String] ipaddr IP address of remote server
12
13
  # @param [Integer] port connection port
13
14
  # @param [Hash] opts options of connection
14
15
  # @option opts [Float, Integer] :connect_timeout seconds timeout for open socket
15
- # @return [TCPSocket] socket
16
+ # @return [Socket] socket
16
17
  #
17
18
  # @raise [ModBusTimeout] timed out attempting to create connection
18
19
  def open_tcp_connection(ipaddr, port, opts = {})
19
20
  @ipaddr, @port = ipaddr, port
20
21
 
21
- opts[:connect_timeout] ||= 1
22
+ timeout = opts[:connect_timeout] ||= 1
22
23
 
23
24
  io = nil
24
25
  begin
25
- timeout(opts[:connect_timeout], ModBusTimeout) do
26
- io = TCPSocket.new(@ipaddr, @port)
27
- end
28
- rescue ModBusTimeout => err
29
- raise ModBusTimeout.new, 'Timed out attempting to create connection'
26
+ io = Socket.tcp(@ipaddr, @port, nil, nil, connect_timeout: timeout)
27
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
28
+ raise ModBusTimeout.new, "Timed out attempting to create connection"
30
29
  end
31
30
 
32
31
  io
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
4
  # TCP client implementation
3
5
  # @example
@@ -13,6 +15,7 @@ module ModBus
13
15
  include TCP
14
16
 
15
17
  protected
18
+
16
19
  # Open TCP\IP connection
17
20
  def open_connection(ipaddr, port = 502, opts = {})
18
21
  open_tcp_connection(ipaddr, port, opts)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
- require 'gserver'
4
+ require "gserver"
3
5
  rescue
4
6
  warn "[WARNING] Install `gserver` gem for use TCPServer"
5
7
  end
@@ -7,16 +9,17 @@ end
7
9
  module ModBus
8
10
  # TCP server implementation
9
11
  # @example
10
- # srv = TCPServer.new(10002, 1)
11
- # srv.coils = [1,0,1,1]
12
- # srv.discrete_inputs = [1,1,0,0]
13
- # srv.holding_registers = [1,2,3,4]
14
- # srv.input_registers = [1,2,3,4]
15
- # srv.debug = true
12
+ # srv = TCPServer.new(10002)
13
+ # slave = srv.with_slave(255)
14
+ # slave.coils = [1,0,1,1]
15
+ # slave.discrete_inputs = [1,1,0,0]
16
+ # slave.holding_registers = [1,2,3,4]
17
+ # slave.input_registers = [1,2,3,4]
18
+ # srv.logger = Logger.new($stdout)
16
19
  # srv.start
17
- class TCPServer < GServer
18
- include Debug
19
- include Server
20
+ class TCPServer < GServer
21
+ include Debug
22
+ include Server
20
23
 
21
24
  # Init server
22
25
  # @param [Integer] port listen port
@@ -24,40 +27,43 @@ module ModBus
24
27
  # @param [Hash] opts options of server
25
28
  # @option opts [String] :host host of server default '127.0.0.1'
26
29
  # @option opts [Float, Integer] :max_connection max of TCP connection with server default 4
27
- def initialize(port = 502, uid = 1, opts = {})
28
- @uid = uid
29
-
30
- warn "[WARNING] Please, use UID = 255. It will be fixed in the next release." if @uid != 0xff
31
-
30
+ def initialize(port = 502, opts = {})
32
31
  opts[:host] = DEFAULT_HOST unless opts[:host]
33
32
  opts[:max_connection] = 4 unless opts[:max_connection]
34
- super(port, host = opts[:host], maxConnection = opts[:max_connection])
35
- end
33
+ super(port, opts[:host], opts[:max_connection])
34
+ end
35
+
36
+ # set the default param
37
+ def with_slave(uid = 255)
38
+ super
39
+ end
36
40
 
37
41
  # Serve requests
38
42
  # @param [TCPSocket] io socket
39
43
  def serve(io)
40
- while not stopped?
44
+ until stopped?
41
45
  header = io.read(7)
42
- tx_id = header[0,2]
43
- proto_id = header[2,2]
44
- len = header[4,2].unpack('n')[0]
46
+ tx_id = header[0, 2]
47
+ proto_id = header[2, 2]
48
+ len = header[4, 2].unpack1("n")
45
49
  unit_id = header.getbyte(6)
46
- if proto_id == "\x00\x00"
47
- req = io.read(len - 1)
48
- if unit_id == @uid || unit_id == 0
49
- log "Server RX (#{req.size} bytes): #{logging_bytes(req)}"
50
-
51
- pdu = exec_req(req, @coils, @discrete_inputs, @holding_registers, @input_registers)
52
-
53
- resp = tx_id + "\0\0" + (pdu.size + 1).to_word + @uid.chr + pdu
54
- log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
55
- io.write resp
56
- else
57
- log "Ignored server RX (invalid unit ID #{unit_id}, #{req.size} bytes): #{logging_bytes(req)}"
58
- end
50
+ next unless proto_id == "\x00\x00"
51
+
52
+ req = io.read(len - 1)
53
+ log "Server RX (#{req.size} bytes): #{logging_bytes(req)}"
54
+
55
+ func = req.getbyte(0)
56
+ params = parse_request(func, req)
57
+ pdu = exec_req(unit_id, func, params, req)
58
+
59
+ if pdu
60
+ resp = "#{tx_id}\x00\x00#{(pdu.size + 1).to_word}#{unit_id.chr}#{pdu}"
61
+ log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
62
+ io.write resp
63
+ else
64
+ log "Ignored server RX (invalid unit ID #{unit_id}, #{req.size} bytes): #{logging_bytes(req)}"
59
65
  end
60
66
  end
61
67
  end
62
- end
68
+ end
63
69
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
4
  # TCP slave implementation
3
5
  # @example
@@ -7,25 +9,26 @@ module ModBus
7
9
  # end
8
10
  # end
9
11
  #
10
- # @see RTUViaTCPClient#open_connection
12
+ # @see TCP#open_tcp_connection
11
13
  # @see Client#with_slave
12
14
  # @see Slave
13
- class TCPSlave < Slave
15
+ class TCPSlave < Client::Slave
14
16
  attr_reader :transaction
15
17
 
16
18
  # @see Slave::initialize
17
19
  def initialize(uid, io)
18
20
  @transaction = 0
19
- super(uid, io)
21
+ super
20
22
  end
21
23
 
22
24
  private
25
+
23
26
  # overide method for RTU over TCP implamentaion
24
27
  # @see Slave#query
25
28
  def send_pdu(pdu)
26
- @transaction = 0 if @transaction.next > 65535
29
+ @transaction = 0 if @transaction.next > 65_535
27
30
  @transaction += 1
28
- msg = @transaction.to_word + "\0\0" + (pdu.size + 1).to_word + @uid.chr + pdu
31
+ msg = "#{@transaction.to_word}\x00\x00#{(pdu.size + 1).to_word}#{@uid.chr}#{pdu}"
29
32
  @io.write msg
30
33
 
31
34
  log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
@@ -36,19 +39,17 @@ module ModBus
36
39
  def read_pdu
37
40
  loop do
38
41
  header = @io.read(7)
39
- if header
40
- trn = header[0,2].unpack('n')[0]
41
- len = header[4,2].unpack('n')[0]
42
- msg = @io.read(len-1)
43
-
44
- log "Rx (#{(header + msg).size} bytes): " + logging_bytes(header + msg)
45
-
46
- if trn == @transaction
47
- return msg
48
- else
49
- log "Transaction number mismatch. A packet is ignored."
50
- end
51
- end
42
+ next unless header
43
+
44
+ trn = header[0, 2].unpack1("n")
45
+ len = header[4, 2].unpack1("n")
46
+ msg = @io.read(len - 1)
47
+
48
+ log "Rx (#{(header + msg).size} bytes): " + logging_bytes(header + msg)
49
+
50
+ return msg if trn == @transaction
51
+
52
+ log "Transaction number mismatch. A packet is ignored."
52
53
  end
53
54
  end
54
55
  end
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
- # Package version
3
- VERSION = '1.3.3'
4
+ VERSION = "2.1.2"
4
5
  end