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
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