rmodbus 1.3.2 → 2.1.2

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.
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 -308
  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
data/lib/rmodbus/proxy.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
4
  # Given a slave and a type of operation, execute a single or multiple read using hash syntax
3
5
  class ReadOnlyProxy
@@ -10,9 +12,9 @@ module ModBus
10
12
  # Note that in the case of multiples, a pluralized version of the method is sent to the slave
11
13
  def [](key)
12
14
  if key.instance_of?(0.class)
13
- @slave.send("read_#{@type}", key, 1)
15
+ @slave.send(:"read_#{@type}", key)
14
16
  elsif key.instance_of?(Range)
15
- @slave.send("read_#{@type}s", key.first, key.count)
17
+ @slave.send(:"read_#{@type}s", key.first, key.count)
16
18
  else
17
19
  raise ModBus::Errors::ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
18
20
  end
@@ -20,22 +22,25 @@ module ModBus
20
22
  end
21
23
 
22
24
  class ReadWriteProxy < ReadOnlyProxy
23
- # Write single or multiple values to a modbus slave depending on whether a Fixnum or a Range was given.
24
- # Note that in the case of multiples, a pluralized version of the method is sent to the slave. Also when
25
- # writing multiple values, the number of elements must match the number of registers in the range or an exception is raised
25
+ # Write single or multiple values to a modbus slave depending on whether a
26
+ # Fixnum or a Range was given.
27
+ # Note that in the case of multiples, a pluralized version of the method is
28
+ # sent to the slave. Also when writing multiple values, the number of
29
+ # elements must match the number of registers in the range or an exception
30
+ # is raised
26
31
  def []=(key, val)
27
32
  if key.instance_of?(0.class)
28
- @slave.send("write_#{@type}", key, val)
33
+ @slave.send(:"write_#{@type}", key, val)
29
34
  elsif key.instance_of?(Range)
30
35
  if key.count != val.size
31
- raise ModBus::Errors::ProxyException, "The size of the range must match the size of the values (#{key.count} != #{val.size})"
36
+ raise ModBus::Errors::ProxyException,
37
+ "The size of the range must match the size of the values (#{key.count} != #{val.size})"
32
38
  end
33
39
 
34
- @slave.send("write_#{@type}s", key.first, val)
40
+ @slave.send(:"write_#{@type}s", key.first, val)
35
41
  else
36
42
  raise ModBus::Errors::ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
37
43
  end
38
44
  end
39
45
  end
40
-
41
46
  end
data/lib/rmodbus/rtu.rb CHANGED
@@ -1,31 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/crc16_modbus"
4
+ require "io/wait"
5
+
1
6
  module ModBus
2
7
  module RTU
3
8
  private
4
9
 
5
10
  # We have to read specific amounts of numbers of bytes from the network depending on the function code and content
6
11
  def read_rtu_response(io)
7
- # Read the slave_id and function code
8
- msg = nil
9
- while msg.nil?
10
- msg = io.read(2)
11
- end
12
+ # Read the slave_id and function code
13
+ msg = read(io, 2)
12
14
 
13
15
  function_code = msg.getbyte(1)
14
16
  case function_code
15
- when 1,2,3,4 then
16
- # read the third byte to find out how much more
17
- # we need to read + CRC
18
- msg += io.read(1)
19
- msg += io.read(msg.getbyte(2)+2)
20
- when 5,6,15,16 then
21
- # We just read in an additional 6 bytes
22
- msg += io.read(6)
23
- when 22 then
24
- msg += io.read(8)
25
- when 0x80..0xff then
26
- msg += io.read(3)
27
- else
28
- raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
17
+ when 1, 2, 3, 4
18
+ # read the third byte to find out how much more
19
+ # we need to read + CRC
20
+ msg += read(io, 1)
21
+ msg + read(io, msg.getbyte(2) + 2)
22
+ when 5, 6, 15, 16
23
+ # We just read in an additional 6 bytes
24
+ msg + read(io, 6)
25
+ when 22
26
+ msg + read(io, 8)
27
+ when 0x80..0xff
28
+ msg + read(io, 3)
29
+ else
30
+ raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
29
31
  end
30
32
  end
31
33
 
@@ -38,127 +40,89 @@ module ModBus
38
40
  end
39
41
  end
40
42
 
41
- def send_rtu_pdu(pdu)
42
- msg = @uid.chr + pdu
43
- msg << crc16(msg).to_word
44
-
45
- clean_input_buff
46
- @io.write msg
43
+ def read(io, len)
44
+ result = +""
45
+ loop do
46
+ this_iter = io.read(len - result.length)
47
+ result.concat(this_iter) if this_iter
48
+ return result if result.length == len
47
49
 
48
- log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
50
+ io.wait_readable
51
+ end
49
52
  end
50
53
 
51
- def read_rtu_pdu
52
- msg = read_rtu_response(@io)
54
+ def read_rtu_request(io)
55
+ # Every message is a minimum of 4 bytes (slave id, function code, crc16)
56
+ msg = read(io, 4)
53
57
 
54
- log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
58
+ # If msg is nil, then our client never sent us anything and it's time to disconnect
59
+ return if msg.nil?
55
60
 
56
- if msg.getbyte(0) == @uid
57
- return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
58
- log "Ignore package: don't match CRC"
59
- else
60
- log "Ignore package: don't match uid ID"
61
- end
62
61
  loop do
63
- #waite timeout
64
- sleep(0.1)
62
+ offset = 0
63
+ crc = msg[-2..].unpack1("S<")
64
+
65
+ # scan the bytestream for a valid CRC
66
+ loop do
67
+ break if offset >= msg.length - 3
68
+
69
+ calculated_crc = Digest::CRC16Modbus.checksum(msg[offset..-3])
70
+ if crc == calculated_crc
71
+ is_response = (msg.getbyte(offset + 1) & 0x80 == 0x80) ||
72
+ (msg.getbyte(offset) == @last_req_uid &&
73
+ msg.getbyte(offset + 1) == @last_req_func &&
74
+ @last_req_timestamp && Time.now.to_f - @last_req_timestamp < 5)
75
+
76
+ params = if is_response
77
+ parse_response(msg.getbyte(offset + 1), msg[(offset + 1)..-3])
78
+ else
79
+ parse_request(msg.getbyte(offset + 1), msg[(offset + 1)..-3])
80
+ end
81
+
82
+ unless params.nil?
83
+ if is_response
84
+ @last_req_uid = @last_req_func = @last_req_timestamp = nil
85
+ else
86
+ @last_req_uid = msg.getbyte(offset)
87
+ @last_req_func = msg.getbyte(offset + 1)
88
+ @last_req_timestamp = Time.now.to_f
89
+ end
90
+ log "Server RX discarding #{offset} bytes: #{logging_bytes(msg[0...offset])}" if offset != 0
91
+ log "Server RX (#{msg.size - offset} bytes): #{logging_bytes(msg[offset..])}"
92
+ return [msg.getbyte(offset), msg.getbyte(offset + 1), params, msg[offset + 1..-3], is_response]
93
+ end
94
+ end
95
+ offset += 1
96
+ end
97
+
98
+ msg.concat(read(io, 1))
99
+ # maximum message size is 256, so that's as far as we have to
100
+ # be able to see at once
101
+ msg = msg[1..] if msg.length > 256
65
102
  end
66
103
  end
67
104
 
68
- def read_rtu_request(io)
69
- # Read the slave_id and function code
70
- msg = io.read(2)
71
-
72
- # If msg is nil, then our client never sent us anything and it's time to disconnect
73
- return if msg.nil?
74
-
75
- function_code = msg.getbyte(1)
76
- if [1, 2, 3, 4, 5, 6].include?(function_code)
77
- # read 6 more bytes and return the message total message
78
- msg += io.read(6)
79
- elsif [15, 16].include?(function_code)
80
- # Read in first register, register count, and data bytes
81
- msg += io.read(5)
82
- # Read in however much data we need to + 2 CRC bytes
83
- msg += io.read(msg.getbyte(6) + 2)
84
- else
85
- raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
86
- end
87
-
88
- log "Server RX (#{msg.size} bytes): #{logging_bytes(msg)}"
89
-
90
- msg
91
- end
92
-
93
- def serv_rtu_requests(io, &blk)
105
+ def serve(io)
94
106
  loop do
95
107
  # read the RTU message
96
- msg = read_rtu_request(io)
97
-
98
- next if msg.nil?
99
-
100
- if msg.getbyte(0) == @uid and msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
101
- pdu = yield msg
102
- resp = @uid.chr + pdu
103
- resp << crc16(resp).to_word
104
- log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
105
- io.write resp
106
- end
107
- end
108
- end
108
+ uid, func, params, pdu, is_response = read_rtu_request(io)
109
109
 
110
- # Calc CRC16 for massage
111
- def crc16(msg)
112
- crc_lo = 0xff
113
- crc_hi = 0xff
110
+ next if uid.nil?
114
111
 
115
- msg.unpack('c*').each do |byte|
116
- i = crc_hi ^ byte
117
- crc_hi = crc_lo ^ CrcHiTable[i]
118
- crc_lo = CrcLoTable[i]
119
- end
112
+ pdu = exec_req(uid, func, params, pdu, is_response: is_response)
113
+ next unless pdu
120
114
 
121
- return ((crc_hi << 8) + crc_lo)
115
+ @last_req_uid = @last_req_func = @last_req_timestamp = nil
116
+ resp = uid.chr + pdu
117
+ resp << [crc16(resp)].pack("S<")
118
+ log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
119
+ io.write resp
120
+ end
122
121
  end
123
122
 
124
- CrcHiTable = [
125
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
126
- 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
127
- 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
128
- 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
129
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
130
- 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
131
- 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
132
- 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
133
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
134
- 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
135
- 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
136
- 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
137
- 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
138
- 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
139
- 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
140
- 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
141
- 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
142
- 0x40]
143
- CrcLoTable = [
144
- 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
145
- 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
146
- 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
147
- 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
148
- 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
149
- 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
150
- 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
151
- 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
152
- 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
153
- 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
154
- 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
155
- 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
156
- 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
157
- 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
158
- 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
159
- 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
160
- 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
161
- 0x40]
162
-
123
+ # Calc CRC16 for massage
124
+ def crc16(msg)
125
+ Digest::CRC16Modbus.checksum(msg)
126
+ end
163
127
  end
164
128
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
4
  # RTU client implementation
3
5
  # @example
@@ -7,16 +9,34 @@ module ModBus
7
9
  # end
8
10
  # end
9
11
  #
12
+ # @example
13
+ # RTUClient.connect('127.0.0.1', 10002) do |cl|
14
+ # cl.with_slave(uid) do |slave|
15
+ # slave.holding_registers[0..100]
16
+ # end
17
+ # end
18
+ #
19
+ # @see TCP#open_tcp_connection
10
20
  # @see SP#open_serial_port
11
21
  # @see Client#initialize
12
22
  class RTUClient < Client
13
23
  include RTU
14
24
  include SP
25
+ include TCP
15
26
 
16
27
  protected
28
+
17
29
  # Open serial port
18
- def open_connection(port, baud=9600, opts = {})
19
- open_serial_port(port, baud, opts)
30
+ def open_connection(port_or_ipaddr, arg = nil, opts = {})
31
+ if port_or_ipaddr.is_a?(IO) || port_or_ipaddr.respond_to?(:read)
32
+ port_or_ipaddr
33
+ elsif File.exist?(port_or_ipaddr) || port_or_ipaddr.start_with?("/dev") || port_or_ipaddr.start_with?("COM")
34
+ arg ||= 9600
35
+ open_serial_port(port_or_ipaddr, arg, opts)
36
+ else
37
+ arg ||= 10_002
38
+ open_tcp_connection(port_or_ipaddr, arg, opts)
39
+ end
20
40
  end
21
41
 
22
42
  def get_slave(uid, io)
@@ -1,12 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
4
  # RTU server implementation
3
5
  # @example
4
- # srv = RTUServer.new('/dev/ttyS1', 9600, 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
6
+ # srv = RTUServer.new('/dev/ttyS1', 9600)
7
+ # slave = srv.with_slave(1)
8
+ # slave.coils = [1,0,1,1]
9
+ # slave.discrete_inputs = [1,1,0,0]
10
+ # slave.holding_registers = [1,2,3,4]
11
+ # slave.input_registers = [1,2,3,4]
12
+ # srv.logger = Logger.new($stdout)
10
13
  # srv.start
11
14
  class RTUServer
12
15
  include Debug
@@ -17,18 +20,19 @@ module ModBus
17
20
  # Init RTU server
18
21
  # @param [Integer] uid slave device
19
22
  # @see SP#open_serial_port
20
- def initialize(port, baud=9600, uid=1, opts = {})
23
+ def initialize(port, baud = 9600, opts = {})
21
24
  Thread.abort_on_exception = true
22
- @sp = open_serial_port(port, baud, opts)
23
- @uid = uid
25
+ @sp = if port.is_a?(IO) || port.respond_to?(:read)
26
+ port
27
+ else
28
+ open_serial_port(port, baud, opts)
29
+ end
24
30
  end
25
31
 
26
32
  # Start server
27
33
  def start
28
34
  @serv = Thread.new do
29
- serv_rtu_requests(@sp) do |msg|
30
- exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
31
- end
35
+ serve(@sp)
32
36
  end
33
37
  end
34
38
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModBus
2
4
  # RTU slave implementation
3
5
  # @example
@@ -10,20 +12,41 @@ module ModBus
10
12
  # @see RTUClient#open_connection
11
13
  # @see Client#with_slave
12
14
  # @see Slave
13
- class RTUSlave < Slave
15
+ class RTUSlave < Client::Slave
14
16
  include RTU
15
17
 
16
18
  private
19
+
17
20
  # overide method for RTU implamentaion
18
21
  # @see Slave#query
19
22
  def send_pdu(pdu)
20
- send_rtu_pdu(pdu)
23
+ msg = @uid.chr + pdu
24
+ msg << [crc16(msg)].pack("S<")
25
+
26
+ clean_input_buff
27
+ @io.write msg
28
+
29
+ log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
21
30
  end
22
31
 
23
32
  # overide method for RTU implamentaion
24
33
  # @see Slave#query
25
34
  def read_pdu
26
- read_rtu_pdu
35
+ msg = read_rtu_response(@io)
36
+
37
+ log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
38
+
39
+ if msg.getbyte(0) == @uid
40
+ return msg[1..-3] if msg[-2, 2].unpack1("S<") == crc16(msg[0..-3])
41
+
42
+ log "Ignore package: don't match CRC"
43
+ else
44
+ log "Ignore package: don't match uid ID"
45
+ end
46
+ loop do
47
+ # waite timeout
48
+ sleep(0.1)
49
+ end
27
50
  end
28
51
  end
29
52
  end
@@ -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 RTUViaTCPServer"
5
7
  end
@@ -7,12 +9,13 @@ end
7
9
  module ModBus
8
10
  # RTU over TCP server implementation
9
11
  # @example
10
- # srv = RTUViaTCPServer.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 = RTUViaTCPServer.new(10002)
13
+ # slave = src.with_slave(1)
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
20
  class RTUViaTCPServer < GServer
18
21
  include Debug
@@ -25,20 +28,10 @@ module ModBus
25
28
  # @param [Hash] opts options of server
26
29
  # @option opts [String] :host host of server default '127.0.0.1'
27
30
  # @option opts [Float, Integer] :max_connection max of TCP connection with server default 4
28
- def initialize(port = 10002, uid = 1, opts = {})
29
- @uid = uid
31
+ def initialize(port = 10_002, opts = {})
30
32
  opts[:host] = DEFAULT_HOST unless opts[:host]
31
33
  opts[:max_connection] = 4 unless opts[:max_connection]
32
- super(port, host = opts[:host], maxConnection = opts[:max_connection])
33
- end
34
-
35
- protected
36
- # Serve requests
37
- # @param [TCPSocket] io socket
38
- def serve(io)
39
- serv_rtu_requests(io) do |msg|
40
- exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
41
- end
34
+ super(port, opts[:host], opts[:max_connection])
42
35
  end
43
36
  end
44
37
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module ModBus
6
+ module Server
7
+ class Slave
8
+ attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers
9
+
10
+ def initialize
11
+ @coils = []
12
+ @discrete_inputs = []
13
+ @holding_registers = []
14
+ @input_registers = []
15
+ end
16
+ end
17
+ end
18
+ end