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
+ module ModBus
2
+ # TCP slave implementation
3
+ # @example
4
+ # TCP.connect('127.0.0.1', 10002) do |cl|
5
+ # cl.with_slave(uid) do |slave|
6
+ # slave.holding_registers[0..100]
7
+ # end
8
+ # end
9
+ #
10
+ # @see TCP#open_tcp_connection
11
+ # @see Client#with_slave
12
+ # @see Slave
13
+ class TCPSlave < Client::Slave
14
+ attr_reader :transaction
15
+
16
+ # @see Slave::initialize
17
+ def initialize(uid, io)
18
+ @transaction = 0
19
+ super(uid, io)
20
+ end
21
+
22
+ private
23
+ # overide method for RTU over TCP implamentaion
24
+ # @see Slave#query
25
+ def send_pdu(pdu)
26
+ @transaction = 0 if @transaction.next > 65535
27
+ @transaction += 1
28
+ msg = @transaction.to_word + "\0\0" + (pdu.size + 1).to_word + @uid.chr + pdu
29
+ @io.write msg
30
+
31
+ log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
32
+ end
33
+
34
+ # overide method for RTU over TCP implamentaion
35
+ # @see Slave#query
36
+ def read_pdu
37
+ loop do
38
+ 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
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module ModBus
2
+ VERSION = '2.0.0'
3
+ end
@@ -0,0 +1,88 @@
1
+ # -*- coding: ascii
2
+ require 'rmodbus'
3
+
4
+ describe ModBus::Client do
5
+ before do
6
+ @cl = ModBus::Client.new
7
+ end
8
+
9
+ it "should give object provider for slave" do
10
+ slave = @cl.with_slave(1)
11
+ slave.uid.should eq(1)
12
+ end
13
+
14
+ it "should give object provider for slave in block" do
15
+ @cl.with_slave(1) do |slave|
16
+ slave.uid.should eq(1)
17
+ end
18
+ end
19
+
20
+ it "should connect with TCP server" do
21
+ ModBus::Client.connect do |cl|
22
+ cl.should be_instance_of(ModBus::Client)
23
+ end
24
+ end
25
+
26
+ it ":new alias :connect" do
27
+ ModBus::Client.new do |cl|
28
+ cl.should be_instance_of(ModBus::Client)
29
+ end
30
+ end
31
+
32
+ it "should close the connection when an exception is raised in the given block" do
33
+ expect {
34
+ ModBus::Client.new do |client|
35
+ client.should_receive(:close)
36
+ raise
37
+ end
38
+ }.to raise_error
39
+ end
40
+
41
+ it 'should common for all slaves :debug flag' do
42
+ @cl.debug = true
43
+ @cl.with_slave(1) do |slave_1|
44
+ slave_1.debug.should be_truthy
45
+ end
46
+ @cl.with_slave(2) do |slave_2|
47
+ slave_2.debug = false
48
+ slave_2.debug.should be_falsey
49
+ end
50
+ end
51
+
52
+ it 'should common for all slaves :raise_exception_on_mismatch flag' do
53
+ @cl.raise_exception_on_mismatch = true
54
+ @cl.with_slave(1) do |slave_1|
55
+ slave_1.raise_exception_on_mismatch.should be_truthy
56
+ end
57
+
58
+ @cl.with_slave(2) do |slave_2|
59
+ slave_2.raise_exception_on_mismatch = false
60
+ slave_2.raise_exception_on_mismatch.should be_falsey
61
+ end
62
+ end
63
+
64
+ it 'should common for all slaves :read_retries options' do
65
+ @cl.read_retries = 5
66
+ @cl.with_slave(1) do |slave_1|
67
+ slave_1.read_retries.should eql(5)
68
+ end
69
+
70
+ @cl.with_slave(2) do |slave_2|
71
+ slave_2.read_retries = 15
72
+ slave_2.read_retries.should eql(15)
73
+ end
74
+ end
75
+
76
+ it 'should common for all slaves :read_retry_timeout options' do
77
+ @cl.read_retry_timeout = 5
78
+ @cl.with_slave(1) do |slave_1|
79
+ slave_1.read_retry_timeout.should eql(5)
80
+ end
81
+
82
+ @cl.with_slave(2) do |slave_2|
83
+ slave_2.read_retry_timeout = 15
84
+ slave_2.read_retry_timeout.should eql(15)
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,120 @@
1
+ # -*- coding: ascii
2
+ require 'rmodbus'
3
+
4
+ describe ModBus::TCPClient do
5
+ before(:all) do
6
+ @srv = ModBus::TCPServer.new(1502)
7
+ srv_slave = @srv.with_slave(1)
8
+ srv_slave.coils = [0] * 8
9
+ srv_slave.discrete_inputs = [0] * 8
10
+ srv_slave.holding_registers = [0] * 8
11
+ srv_slave.input_registers = [0] * 8
12
+ @srv.start
13
+
14
+ @cl = ModBus::TCPClient.new('127.0.0.1', 1502)
15
+ @slave = @cl.with_slave(1)
16
+ end
17
+
18
+ it "should raise ProxException" do
19
+ lambda { @slave.holding_registers[0..2] = [0,0] }.should raise_error(ModBus::Errors::ProxyException)
20
+ end
21
+
22
+ # Read coil status
23
+ it "should read coil status" do
24
+ @slave.read_coils(0, 4).should == [0] * 4
25
+ end
26
+
27
+ it "should raise exception if illegal data address" do
28
+ lambda { @slave.read_coils(501, 34) }.should raise_error(ModBus::Errors::IllegalDataAddress)
29
+ end
30
+
31
+ it "should raise exception if too many data" do
32
+ lambda { @slave.read_coils(0, 0x07D1) }.should raise_error(ModBus::Errors::IllegalDataValue)
33
+ end
34
+
35
+ # Read input status
36
+ it "should read discrete inputs" do
37
+ @slave.read_discrete_inputs(0, 4).should == [0] * 4
38
+ end
39
+
40
+ it "should raise exception if illegal data address" do
41
+ lambda { @slave.read_discrete_inputs(50, 23) }.should raise_error(ModBus::Errors::IllegalDataAddress)
42
+ end
43
+
44
+ it "should raise exception if too many data" do
45
+ lambda { @slave.read_discrete_inputs(0, 0x07D1) }.should raise_error(ModBus::Errors::IllegalDataValue)
46
+ end
47
+
48
+ # Read holding registers
49
+ it "should read discrete inputs" do
50
+ @slave.read_holding_registers(0, 4).should == [0, 0, 0, 0]
51
+ end
52
+
53
+ it "should raise exception if illegal data address" do
54
+ lambda { @slave.read_holding_registers(402, 99) }.should raise_error(ModBus::Errors::IllegalDataAddress)
55
+ end
56
+
57
+
58
+ it "should raise exception if too many data" do
59
+ lambda { @slave.read_holding_registers(0, 0x007E) }.should raise_error(ModBus::Errors::IllegalDataValue)
60
+ end
61
+
62
+ # Read input registers
63
+ it "should read discrete inputs" do
64
+ @slave.read_input_registers(0, 4).should == [0, 0, 0, 0]
65
+ end
66
+
67
+ it "should raise exception if illegal data address" do
68
+ lambda { @slave.read_input_registers(402, 9) }.should raise_error(ModBus::Errors::IllegalDataAddress)
69
+ end
70
+
71
+ it "should raise exception if too many data" do
72
+ lambda { @slave.read_input_registers(0, 0x007E) }.should raise_error(ModBus::Errors::IllegalDataValue)
73
+ end
74
+
75
+ # Force single coil
76
+ it "should force single coil" do
77
+ @slave.write_single_coil(4, 1).should == @slave
78
+ @slave.read_coils(4, 4).should == [1, 0, 0, 0]
79
+ end
80
+
81
+ it "should raise exception if illegal data address" do
82
+ lambda { @slave.write_single_coil(501, true) }.should raise_error(ModBus::Errors::IllegalDataAddress)
83
+ end
84
+
85
+ # Preset single register
86
+ it "should preset single register" do
87
+ @slave.write_single_register(4, 0x0AA0).should == @slave
88
+ @slave.read_holding_registers(4, 1).should == [0x0AA0]
89
+ end
90
+
91
+ it "should raise exception if illegal data address" do
92
+ lambda { @slave.write_single_register(501, 0x0AA0) }.should raise_error(ModBus::Errors::IllegalDataAddress)
93
+ end
94
+
95
+ # Force multiple coils
96
+ it "should force multiple coils" do
97
+ @slave.write_multiple_coils(4, [0,1,0,1]).should == @slave
98
+ @slave.read_coils(3, 5).should == [0,0,1,0,1]
99
+ end
100
+
101
+ it "should raise exception if illegal data address" do
102
+ lambda { @slave.write_multiple_coils(501, [1,0]) }.should raise_error(ModBus::Errors::IllegalDataAddress)
103
+ end
104
+
105
+ # Preset multiple registers
106
+ it "should preset multiple registers" do
107
+ @slave.write_multiple_registers(4, [1, 2, 3, 0xAACC]).should == @slave
108
+ @slave.read_holding_registers(3, 5).should == [0, 1, 2, 3, 0xAACC]
109
+ end
110
+
111
+ it "should raise exception if illegal data address" do
112
+ lambda { @slave.write_multiple_registers(501, [1, 2]) }.should raise_error(ModBus::Errors::IllegalDataAddress)
113
+ end
114
+
115
+ after(:all) do
116
+ @cl.close unless @cl.closed?
117
+ @srv.stop
118
+ end
119
+
120
+ end
data/spec/ext_spec.rb ADDED
@@ -0,0 +1,52 @@
1
+ # -*- coding: ascii
2
+ require 'rmodbus'
3
+
4
+ describe Array do
5
+ before do
6
+ @arr = [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
7
+ @test = [0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0]
8
+ end
9
+
10
+ it "should return string reprisent 16bit" do
11
+ @arr.pack_to_word.should == "\xcd\x6b\x5"
12
+ end
13
+
14
+ it "fixed bug for divisible 8 data " do
15
+ ([0] * 8).pack_to_word.should == "\x00"
16
+ end
17
+
18
+ it "should unpack to @test" do
19
+ "test".unpack_bits == @test
20
+ end
21
+
22
+ it "should turn an array into 32b ints" do
23
+ [20342, 17344].to_32i.should == [1136676726]
24
+ [20342, 17344, 20342, 17344].to_32i.size.should == 2
25
+ end
26
+
27
+ it "should turn an array into 32b floats big endian" do
28
+ [20342, 17344].to_32f[0].should be_within(0.1).of(384.620788574219)
29
+ [20342, 17344, 20342, 17344].to_32f.size.should == 2
30
+ end
31
+
32
+ it "should turn a an array into 32b floats (little endian)" do
33
+ [17344, 20342].to_32f_le[0].should be_within(0.1).of(384.620788574219)
34
+ [17344, 20342, 17344, 20342].to_32f_le.size.should == 2
35
+ end
36
+
37
+ it "should turn an array from 32b ints into 16b ints, big endian" do
38
+ [1136676726].from_32i.should == [20342, 17344]
39
+ [1136676726, 1136676725].from_32i.should == [20342, 17344, 20341, 17344]
40
+ end
41
+
42
+ it "should turn an array from 32b floats into 16b ints, big endian" do
43
+ [384.620788].from_32f.should == [20342, 17344]
44
+ [384.620788, 384.620788].from_32f.should == [20342, 17344, 20342, 17344]
45
+ end
46
+
47
+ it "should raise exception if uneven number of elements" do
48
+ lambda { [20342, 17344, 123].to_32f }.should raise_error(StandardError)
49
+ lambda { [20342, 17344, 123].to_32i }.should raise_error(StandardError)
50
+ end
51
+ end
52
+
@@ -0,0 +1,89 @@
1
+ # -*- coding: ascii
2
+ require 'rmodbus'
3
+
4
+ describe ModBus::TCPClient do
5
+ before(:each) do
6
+ @uid = 1
7
+ @sock = double('Socket')
8
+ @adu = "\000\001\000\000\000\001\001"
9
+
10
+ Socket.should_receive(:tcp).with('127.0.0.1', 1502, nil, nil, hash_including(:connect_timeout)).and_return(@sock)
11
+ @sock.stub(:read).with(0).and_return('')
12
+
13
+ @slave = ModBus::TCPClient.new('127.0.0.1', 1502).with_slave(@uid)
14
+ @slave.debug = true
15
+ end
16
+
17
+ it 'should log rec\send bytes' do
18
+ request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
19
+ mock_query(request,response)
20
+ $stdout.should_receive(:puts).with("Tx (12 bytes): [00][01][00][00][00][06][01][03][00][6b][00][03]")
21
+ $stdout.should_receive(:puts).with("Rx (15 bytes): [00][01][00][00][00][09][01][03][06][02][2b][00][00][00][64]")
22
+ @slave.query(request)
23
+ end
24
+
25
+ it "should don't logging if debug disable" do
26
+ @slave.debug = false
27
+ request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
28
+ mock_query(request,response)
29
+ @slave.query(request)
30
+ end
31
+
32
+ it "should log warn message if transaction mismatch" do
33
+ @adu[0,2] = @slave.transaction.next.to_word
34
+ @sock.should_receive(:write).with(@adu)
35
+ @sock.should_receive(:read).with(7).and_return("\000\002\000\000\000\001" + @uid.chr)
36
+ @sock.should_receive(:read).with(7).and_return("\000\001\000\000\000\001" + @uid.chr)
37
+
38
+ $stdout.should_receive(:puts).with("Tx (7 bytes): [00][01][00][00][00][01][01]")
39
+ $stdout.should_receive(:puts).with("Rx (7 bytes): [00][02][00][00][00][01][01]")
40
+ $stdout.should_receive(:puts).with("Transaction number mismatch. A packet is ignored.")
41
+ $stdout.should_receive(:puts).with("Rx (7 bytes): [00][01][00][00][00][01][01]")
42
+
43
+ @slave.query('')
44
+ end
45
+
46
+ def mock_query(request, response)
47
+ @adu = @slave.transaction.next.to_word + "\x0\x0\x0\x9" + @uid.chr + request
48
+ @sock.should_receive(:write).with(@adu[0,4] + "\0\6" + @uid.chr + request)
49
+ @sock.should_receive(:read).with(7).and_return(@adu[0,7])
50
+ @sock.should_receive(:read).with(8).and_return(response)
51
+ end
52
+ end
53
+
54
+ begin
55
+ require "serialport"
56
+ describe ModBus::RTUClient do
57
+ before do
58
+ @sp = double('Serial port')
59
+
60
+ SerialPort.should_receive(:new).with("/dev/port1", 9600, 7, 2, SerialPort::ODD).and_return(@sp)
61
+ SerialPort.stub(:public_method_defined?).with(:flush_input).and_return(true)
62
+
63
+ @sp.stub(:class).and_return(SerialPort)
64
+ @sp.should_receive(:flow_control=).with(SerialPort::NONE)
65
+ @sp.stub(:read_timeout=)
66
+ @sp.stub(:flush_input)
67
+
68
+ @slave = ModBus::RTUClient.new("/dev/port1", 9600, :data_bits => 7, :stop_bits => 2, :parity => SerialPort::ODD).with_slave(1)
69
+ @slave.read_retries = 0
70
+
71
+ end
72
+
73
+ it 'should log rec\send bytes' do
74
+ request = "\x3\x0\x1\x0\x1"
75
+ @sp.should_receive(:write).with("\1#{request}\xd5\xca")
76
+ @sp.should_receive(:flush_input) # Clean a garbage
77
+ @sp.should_receive(:read).with(2).and_return("\x1\x3")
78
+ @sp.should_receive(:read).with(1).and_return("\x2")
79
+ @sp.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
80
+
81
+ @slave.debug = true
82
+ $stdout.should_receive(:puts).with("Tx (8 bytes): [01][03][00][01][00][01][d5][ca]")
83
+ $stdout.should_receive(:puts).with("Rx (7 bytes): [01][03][02][ff][ff][b9][f4]")
84
+
85
+ @slave.query(request).should == "\xff\xff"
86
+ end
87
+ end
88
+ rescue LoadError
89
+ end
@@ -0,0 +1,74 @@
1
+ # -*- coding: ascii
2
+ require 'rmodbus'
3
+
4
+ describe Array do
5
+ before do
6
+ @slave = double('ModBus Slave')
7
+ @coil_proxy = ModBus::ReadWriteProxy.new(@slave, :coil)
8
+ @discrete_input_proxy = ModBus::ReadOnlyProxy.new(@slave, :discrete_input)
9
+ @holding_register_proxy = ModBus::ReadWriteProxy.new(@slave, :holding_register)
10
+ @input_register_proxy = ModBus::ReadOnlyProxy.new(@slave, :input_register)
11
+ end
12
+
13
+ # Handle all of the coil methods
14
+ it "should call read_coil" do
15
+ @slave.should_receive(:read_coil).with(0, 1)
16
+ @coil_proxy[0]
17
+ end
18
+ it "should call read_coils" do
19
+ @slave.should_receive(:read_coils).with(0, 2)
20
+ @coil_proxy[0..1]
21
+ end
22
+ it "should call write_coil" do
23
+ @slave.should_receive(:write_coil).with(0, 1)
24
+ @coil_proxy[0] = 1
25
+ end
26
+ it "should call write_coils" do
27
+ @slave.should_receive(:write_coils).with(0, [0, 0])
28
+ @coil_proxy[0..1] = [0, 0]
29
+ end
30
+
31
+
32
+ # Discrete input tests
33
+ it "should call read_discrete_input" do
34
+ @slave.should_receive(:read_discrete_input).with(0, 1)
35
+ @discrete_input_proxy[0]
36
+ end
37
+
38
+ it "should call read_discrete_inputs" do
39
+ @slave.should_receive(:read_discrete_inputs).with(0, 2)
40
+ @discrete_input_proxy[0..1]
41
+ end
42
+
43
+
44
+ # Holding Register Tess
45
+ it "should call read_holding_register" do
46
+ @slave.should_receive(:read_holding_register).with(0, 1)
47
+ @holding_register_proxy[0]
48
+ end
49
+ it "should call read_holding_registers" do
50
+ @slave.should_receive(:read_holding_registers).with(0, 2)
51
+ @holding_register_proxy[0..1]
52
+ end
53
+ it "should call write_holding_register" do
54
+ @slave.should_receive(:write_holding_register).with(0, 1)
55
+ @holding_register_proxy[0] = 1
56
+ end
57
+ it "should call write_holding_registers" do
58
+ @slave.should_receive(:write_holding_registers).with(0, [0, 0])
59
+ @holding_register_proxy[0..1] = [0, 0]
60
+ end
61
+
62
+
63
+ # Input Register Tests
64
+ it "should call read_discrete_input" do
65
+ @slave.should_receive(:read_input_register).with(0, 1)
66
+ @input_register_proxy[0]
67
+ end
68
+
69
+ it "should call read_discrete_inputs" do
70
+ @slave.should_receive(:read_input_registers).with(0, 2)
71
+ @input_register_proxy[0..1]
72
+ end
73
+ end
74
+