rmodbus 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/NEWS.md +52 -0
  2. data/README.md +87 -0
  3. data/Rakefile +22 -36
  4. data/examples/perfomance_rtu.rb +35 -37
  5. data/examples/perfomance_tcp.rb +36 -38
  6. data/examples/use_rtu_via_tcp_modbus.rb +8 -5
  7. data/examples/use_tcp_modbus.rb +10 -6
  8. data/lib/rmodbus/client.rb +52 -174
  9. data/lib/rmodbus/common.rb +45 -18
  10. data/lib/rmodbus/{exceptions.rb → errors.rb} +3 -0
  11. data/lib/rmodbus/ext.rb +25 -2
  12. data/lib/rmodbus/proxy.rb +54 -0
  13. data/lib/rmodbus/{crc16.rb → rtu.rb} +73 -2
  14. data/lib/rmodbus/rtu_client.rb +20 -116
  15. data/lib/rmodbus/rtu_server.rb +28 -57
  16. data/lib/rmodbus/rtu_slave.rb +59 -0
  17. data/lib/rmodbus/rtu_via_tcp_client.rb +22 -86
  18. data/lib/rmodbus/rtu_via_tcp_server.rb +31 -95
  19. data/lib/rmodbus/rtu_via_tcp_slave.rb +58 -0
  20. data/lib/rmodbus/{parsers.rb → server.rb} +24 -15
  21. data/lib/rmodbus/slave.rb +268 -0
  22. data/lib/rmodbus/sp.rb +45 -0
  23. data/lib/rmodbus/tcp.rb +49 -0
  24. data/lib/rmodbus/tcp_client.rb +19 -88
  25. data/lib/rmodbus/tcp_server.rb +16 -19
  26. data/lib/rmodbus/tcp_slave.rb +64 -0
  27. data/lib/rmodbus/version.rb +17 -0
  28. data/lib/rmodbus.rb +20 -4
  29. data/spec/client_spec.rb +19 -45
  30. data/spec/exception_spec.rb +26 -27
  31. data/spec/ext_spec.rb +24 -1
  32. data/spec/logging_spec.rb +31 -37
  33. data/spec/proxy_spec.rb +73 -0
  34. data/spec/read_rtu_response_spec.rb +2 -4
  35. data/spec/rtu_client_spec.rb +17 -19
  36. data/spec/rtu_server_spec.rb +1 -3
  37. data/spec/rtu_via_tcp_client_spec.rb +69 -63
  38. data/spec/slave_spec.rb +55 -0
  39. data/spec/tcp_client_spec.rb +77 -69
  40. data/spec/tcp_server_spec.rb +34 -49
  41. metadata +123 -37
  42. data/AUTHORS +0 -3
  43. data/ChangeLog +0 -82
  44. data/LICENSE +0 -675
  45. data/README +0 -53
  46. data/examples/add_new_function.rb +0 -19
@@ -1,6 +1,6 @@
1
1
  # RModBus - free implementation of ModBus protocol in Ruby.
2
2
  #
3
- # Copyright (C) 2010 Timin Aleksey
3
+ # Copyright (C) 2010-2011 Timin Aleksey
4
4
  #
5
5
  # This program is free software: you can redistribute it and/or modify
6
6
  # it under the terms of the GNU General Public License as published by
@@ -11,78 +11,49 @@
11
11
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
13
  # GNU General Public License for more details.
14
-
15
- require 'rmodbus/parsers'
16
-
17
- begin
18
- require 'rubygems'
19
- rescue
20
- end
21
-
22
14
  require 'serialport'
23
15
 
24
16
  module ModBus
25
-
17
+ # RTU server implementation
18
+ # @example
19
+ # srv = RTUServer.new('/dev/ttyS1', 9600, 1)
20
+ # srv.coils = [1,0,1,1]
21
+ # srv.discrete_inputs = [1,1,0,0]
22
+ # srv.holding_registers = [1,2,3,4]
23
+ # srv.input_registers = [1,2,3,4]
24
+ # srv.debug = true
25
+ # srv.start
26
26
  class RTUServer
27
- include Parsers
28
- include CRC16
29
-
30
- attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers
31
- attr_reader :port, :baud, :slave, :data_bits, :stop_bits, :parity
32
-
33
- def discret_inputs
34
- warn "[DEPRECATION] `discret_inputs` is deprecated. Please use `discrete_inputs` instead."
35
- @discrete_inputs
36
- end
37
-
38
- def discret_inputs=(val)
39
- warn "[DEPRECATION] `discret_inputs=` is deprecated. Please use `discrete_inputs=` instead."
40
- @discrete_inputs=val
41
- end
42
-
43
-
44
- def initialize(port, baud=9600, slaveaddr=1, options = {})
45
- Thread.abort_on_exception = true
46
-
47
- @port, @baud = port, baud
48
- @data_bits, @stop_bits, @parity = 8, 1, SerialPort::NONE
49
-
50
- @data_bits = options[:data_bits] unless options[:data_bits].nil?
51
- @stop_bits = options[:stop_bits] unless options[:stop_bits].nil?
52
- @parity = options[:parity] unless options[:parity].nil?
53
-
54
- @sp = SerialPort.new(@port, @baud, @data_bits, @stop_bits, @parity)
55
- @sp.read_timeout = 5
56
-
57
- @coils = []
58
- @discrete_inputs = []
59
- @holding_registers = []
60
- @input_registers = []
61
- @slave = slaveaddr
27
+ include Common
28
+ include Server
29
+ include RTU
30
+ include SP
31
+
32
+ # Init RTU server
33
+ # @param [Integer] uid slave device
34
+ # @see SP#open_serial_port
35
+ def initialize(port, baud=9600, uid=1, opts = {})
36
+ Thread.abort_on_exception = true
37
+ @sp = open_serial_port(port, baud, opts)
38
+ @uid = uid
62
39
  end
63
40
 
41
+ # Start server
64
42
  def start
65
43
  @serv = Thread.new do
66
- loop do
67
- req = ''
68
- while req.size == 0
69
- req = @sp.read
70
- end
71
- if req.getbyte(0) == @slave and req[-2,2].unpack('n')[0] == crc16(req[0..-3])
72
- pdu = exec_req(req[1..-1], @coils, @discrete_inputs, @holding_registers, @input_registers)
73
- resp = @slave.chr + pdu
74
- resp << crc16(resp).to_word
75
- @sp.write resp
76
- end
44
+ serv_rtu_requests(@sp) do |msg|
45
+ exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
77
46
  end
78
47
  end
79
48
  end
80
49
 
50
+ # Stop server
81
51
  def stop
82
52
  Thread.kill(@serv)
83
- @sp.close
53
+ @sp.close
84
54
  end
85
55
 
56
+ # Join server
86
57
  def join
87
58
  @serv.join
88
59
  end
@@ -0,0 +1,59 @@
1
+ # RModBus - free implementation of ModBus protocol in Ruby.
2
+ #
3
+ # Copyright (C) 2011 Timin Aleksey
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ module ModBus
15
+ # RTU slave implementation
16
+ # @example
17
+ # RTUClient.connect(port, baud, opts) do |cl|
18
+ # cl.with_slave(uid) do |slave|
19
+ # slave.holding_registers[0..100]
20
+ # end
21
+ # end
22
+ #
23
+ # @see RTUClient#open_connection
24
+ # @see Client#with_slave
25
+ # @see Slave
26
+ class RTUSlave < Slave
27
+ include RTU
28
+
29
+ protected
30
+
31
+ # overide method for RTU implamentaion
32
+ # @see Slave#query
33
+ def send_pdu(pdu)
34
+ msg = @uid.chr + pdu
35
+ msg << crc16(msg).to_word
36
+ @io.write msg
37
+
38
+ log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
39
+ end
40
+
41
+ # overide method for RTU implamentaion
42
+ # @see Slave#query
43
+ def read_pdu
44
+ msg = read_rtu_response(@io)
45
+
46
+ log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
47
+
48
+ if msg.getbyte(0) == @uid
49
+ return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
50
+ log "Ignore package: don't match CRC"
51
+ else
52
+ log "Ignore package: don't match uid ID"
53
+ end
54
+ loop do
55
+ #waite timeout
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,6 +1,6 @@
1
1
  # RModBus - free implementation of ModBus protocol in Ruby.
2
2
  #
3
- # Copyright (C) 2009 Timin Aleksey
3
+ # Copyright (C) 2009-2011 Timin Aleksey
4
4
  # Copyright (C) 2010 Kelley Reynolds
5
5
  #
6
6
  # This program is free software: you can redistribute it and/or modify
@@ -12,94 +12,30 @@
12
12
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
13
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
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
15
  module ModBus
21
-
16
+ # RTU over TCP client implementation
17
+ # @example
18
+ # RTUViaTCPClient.connect('127.0.0.1', 10002) do |cl|
19
+ # cl.with_slave(uid) do |slave|
20
+ # slave.holding_registers[0..100]
21
+ # end
22
+ # end
23
+ #
24
+ # @see RTUViaTCPClient#open_connection
25
+ # @see Client#initialize
22
26
  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?
27
+ include RTU
28
+ include TCP
29
+
30
+ protected
31
+ # Open TCP\IP connection
32
+ # @see TCP#open_tcp_connection
33
+ def open_connection(ipaddr, port = 10002, opts = {})
34
+ io = open_tcp_connection(ipaddr, port, opts)
72
35
  end
73
36
 
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
37
+ def get_slave(uid, io)
38
+ RTUViaTCPSlave.new(uid, io)
39
+ end
104
40
  end
105
41
  end
@@ -13,102 +13,38 @@
13
13
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
14
  # GNU General Public License for more details.
15
15
 
16
- require 'rmodbus/parsers'
17
16
  require 'gserver'
18
17
 
19
18
  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
19
+ # RTU over TCP server implementation
20
+ # @example
21
+ # srv = RTUViaTCPServer.new(10002, 1)
22
+ # srv.coils = [1,0,1,1]
23
+ # srv.discrete_inputs = [1,1,0,0]
24
+ # srv.holding_registers = [1,2,3,4]
25
+ # srv.input_registers = [1,2,3,4]
26
+ # srv.debug = true
27
+ # srv.start
28
+ class RTUViaTCPServer < GServer
29
+ include Common
30
+ include RTU
31
+ include Server
32
+
33
+ # Init server
34
+ # @param [Integer] port listen port
35
+ # @param [Integer] uid slave device
36
+ def initialize(port = 10002, uid = 1)
37
+ @uid = uid
38
+ super(port)
39
+ end
40
+
41
+ protected
42
+ # Serve requests
43
+ # @param [TCPSocket] io socket
44
+ def serve(io)
45
+ serv_rtu_requests(io) do |msg|
46
+ exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
47
+ end
48
+ end
49
+ end
114
50
  end
@@ -0,0 +1,58 @@
1
+ # RModBus - free implementation of ModBus protocol in Ruby.
2
+ #
3
+ # Copyright (C) 2011 Timin Aleksey
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ module ModBus
15
+ # RTU over TCP slave implementation
16
+ # @example
17
+ # RTUViaTCP.connect('127.0.0.1', 10002) do |cl|
18
+ # cl.with_slave(uid) do |slave|
19
+ # slave.holding_registers[0..100]
20
+ # end
21
+ # end
22
+ #
23
+ # @see RTUViaTCPClient#open_connection
24
+ # @see Client#with_slave
25
+ # @see Slave
26
+ class RTUViaTCPSlave < Slave
27
+ include RTU
28
+
29
+ protected
30
+ # overide method for RTU over TCP implamentaion
31
+ # @see Slave#query
32
+ def send_pdu(pdu)
33
+ msg = @uid.chr + pdu
34
+ msg << crc16(msg).to_word
35
+ @io.write msg
36
+
37
+ log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
38
+ end
39
+
40
+ # overide method for RTU over TCP implamentaion
41
+ # @see Slave#query
42
+ def read_pdu
43
+ # Read the response appropriately
44
+ msg = read_rtu_response(@io)
45
+
46
+ log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
47
+ if msg.getbyte(0) == @uid
48
+ return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
49
+ log "Ignore package: don't match CRC"
50
+ else
51
+ log "Ignore package: don't match uid ID"
52
+ end
53
+ loop do
54
+ #waite timeout
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,6 +1,7 @@
1
- # RModBus - free implementation of ModBus protocol in purge Ruby.
1
+ # RModBus - free implementation of ModBus protocol in Ruby.
2
2
  #
3
3
  # Copyright (C) 2010 Timin Aleksey
4
+ # Copyright (C) 2010 Kelley Reynolds
4
5
  #
5
6
  # This program is free software: you can redistribute it and/or modify
6
7
  # it under the terms of the GNU General Public License as published by
@@ -13,17 +14,25 @@
13
14
  # GNU General Public License for more details.
14
15
 
15
16
  module ModBus
16
- module Parsers
17
-
17
+ # Module for implementation ModBus server
18
+ module Server
18
19
  Funcs = [1,2,3,4,5,6,15,16]
19
20
 
21
+ attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers, :uid
22
+ @coils = []
23
+ @discrete_inputs = []
24
+ @holding_registers =[]
25
+ @input_registers = []
26
+
27
+ private
28
+
20
29
  def exec_req(req, coils, discrete_inputs, holding_registers, input_registers)
21
30
  func = req.getbyte(0)
22
-
31
+
23
32
  unless Funcs.include?(func)
24
33
  params = { :err => 1 }
25
34
  end
26
-
35
+
27
36
  case func
28
37
  when 1
29
38
  params = parse_read_func(req, coils)
@@ -47,7 +56,7 @@ module ModBus
47
56
  if params[:err] == 0
48
57
  pdu = func.chr + (params[:quant] * 2).chr + input_registers[params[:addr],params[:quant]].pack('n*')
49
58
  end
50
- when 5
59
+ when 5
51
60
  params = parse_write_coil_func(req)
52
61
  if params[:err] == 0
53
62
  coils[params[:addr]] = params[:val]
@@ -72,24 +81,23 @@ module ModBus
72
81
  pdu = req[0,5]
73
82
  end
74
83
  end
75
-
84
+
76
85
  if params[:err] == 0
77
- pdu
86
+ pdu
78
87
  else
79
88
  pdu = (func | 0x80).chr + params[:err].chr
80
89
  end
81
90
  end
82
91
 
83
- private
84
92
  def parse_read_func(req, field)
85
93
  quant = req[3,2].unpack('n')[0]
86
94
 
87
95
  return { :err => 3} unless quant <= 0x7d
88
-
96
+
89
97
  addr = req[1,2].unpack('n')[0]
90
98
  return { :err => 2 } unless addr + quant <= field.size
91
-
92
- return { :err => 0, :quant => quant, :addr => addr }
99
+
100
+ return { :err => 0, :quant => quant, :addr => addr }
93
101
  end
94
102
 
95
103
  def parse_write_coil_func(req)
@@ -98,9 +106,9 @@ module ModBus
98
106
 
99
107
  val = req[3,2].unpack('n')[0]
100
108
  return { :err => 3 } unless val == 0 or val == 0xff00
101
-
109
+
102
110
  val = 1 if val == 0xff00
103
- return { :err => 0, :addr => addr, :val => val }
111
+ return { :err => 0, :addr => addr, :val => val }
104
112
  end
105
113
 
106
114
  def parse_write_register_func(req)
@@ -109,7 +117,7 @@ module ModBus
109
117
 
110
118
  val = req[3,2].unpack('n')[0]
111
119
 
112
- return { :err => 0, :addr => addr, :val => val }
120
+ return { :err => 0, :addr => addr, :val => val }
113
121
  end
114
122
 
115
123
  def parse_write_multiple_coils_func(req)
@@ -129,5 +137,6 @@ module ModBus
129
137
  end
130
138
  params
131
139
  end
140
+
132
141
  end
133
142
  end