rmodbus 1.0.0-java
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.
- data/NEWS.md +52 -0
- data/README.md +87 -0
- data/Rakefile +31 -0
- data/examples/perfomance_rtu.rb +56 -0
- data/examples/perfomance_tcp.rb +55 -0
- data/examples/use_rtu_via_tcp_modbus.rb +22 -0
- data/examples/use_tcp_modbus.rb +23 -0
- data/lib/rmodbus/client.rb +91 -0
- data/lib/rmodbus/common.rb +50 -0
- data/lib/rmodbus/errors.rb +50 -0
- data/lib/rmodbus/ext.rb +88 -0
- data/lib/rmodbus/proxy.rb +54 -0
- data/lib/rmodbus/rtu.rb +140 -0
- data/lib/rmodbus/rtu_client.rb +41 -0
- data/lib/rmodbus/rtu_server.rb +61 -0
- data/lib/rmodbus/rtu_slave.rb +59 -0
- data/lib/rmodbus/rtu_via_tcp_client.rb +41 -0
- data/lib/rmodbus/rtu_via_tcp_server.rb +50 -0
- data/lib/rmodbus/rtu_via_tcp_slave.rb +58 -0
- data/lib/rmodbus/server.rb +142 -0
- data/lib/rmodbus/slave.rb +268 -0
- data/lib/rmodbus/sp.rb +45 -0
- data/lib/rmodbus/tcp.rb +49 -0
- data/lib/rmodbus/tcp_client.rb +39 -0
- data/lib/rmodbus/tcp_server.rb +61 -0
- data/lib/rmodbus/tcp_slave.rb +64 -0
- data/lib/rmodbus/version.rb +17 -0
- data/lib/rmodbus.rb +35 -0
- data/spec/client_spec.rb +31 -0
- data/spec/exception_spec.rb +116 -0
- data/spec/ext_spec.rb +46 -0
- data/spec/logging_spec.rb +65 -0
- data/spec/proxy_spec.rb +73 -0
- data/spec/read_rtu_response_spec.rb +91 -0
- data/spec/rtu_client_spec.rb +74 -0
- data/spec/rtu_server_spec.rb +29 -0
- data/spec/rtu_via_tcp_client_spec.rb +78 -0
- data/spec/slave_spec.rb +55 -0
- data/spec/tcp_client_spec.rb +81 -0
- data/spec/tcp_server_spec.rb +99 -0
- metadata +165 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
|
3
|
+
include ModBus::Errors
|
4
|
+
|
5
|
+
describe ModBus::TCPClient do
|
6
|
+
before(:all) do
|
7
|
+
@srv = ModBus::TCPServer.new(1502, 1)
|
8
|
+
@srv.coils = [0] * 8
|
9
|
+
@srv.discrete_inputs = [0] * 8
|
10
|
+
@srv.holding_registers = [0] * 8
|
11
|
+
@srv.input_registers = [0] * 8
|
12
|
+
@srv.start
|
13
|
+
|
14
|
+
@cl = TCPClient.new('127.0.0.1', 1502)
|
15
|
+
@slave = @cl.with_slave(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Read coil status
|
19
|
+
it "should read coil status" do
|
20
|
+
@slave.read_coils(0, 4).should == [0] * 4
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise exception if illegal data address" do
|
24
|
+
lambda { @slave.read_coils(501, 34) }.should raise_error(IllegalDataAddress)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should raise exception if too many data" do
|
28
|
+
lambda { @slave.read_coils(0, 0x07D1) }.should raise_error(IllegalDataValue)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Read input status
|
32
|
+
it "should read discrete inputs" do
|
33
|
+
@slave.read_discrete_inputs(0, 4).should == [0] * 4
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should raise exception if illegal data address" do
|
37
|
+
lambda { @slave.read_discrete_inputs(50, 23) }.should raise_error(IllegalDataAddress)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should raise exception if too many data" do
|
41
|
+
lambda { @slave.read_discrete_inputs(0, 0x07D1) }.should raise_error(IllegalDataValue)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Read holding registers
|
45
|
+
it "should read discrete inputs" do
|
46
|
+
@slave.read_holding_registers(0, 4).should == [0, 0, 0, 0]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should raise exception if illegal data address" do
|
50
|
+
lambda { @slave.read_holding_registers(402, 99) }.should raise_error(IllegalDataAddress)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
it "should raise exception if too many data" do
|
55
|
+
lambda { @slave.read_holding_registers(0, 0x007E) }.should raise_error(IllegalDataValue)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Read input registers
|
59
|
+
it "should read discrete inputs" do
|
60
|
+
@slave.read_input_registers(0, 4).should == [0, 0, 0, 0]
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise exception if illegal data address" do
|
64
|
+
lambda { @slave.read_input_registers(402, 9) }.should raise_error(IllegalDataAddress)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should raise exception if too many data" do
|
68
|
+
lambda { @slave.read_input_registers(0, 0x007E) }.should raise_error(IllegalDataValue)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Force single coil
|
72
|
+
it "should force single coil" do
|
73
|
+
@slave.write_single_coil(4, 1).should == @slave
|
74
|
+
@slave.read_coils(4, 4).should == [1, 0, 0, 0]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should raise exception if illegal data address" do
|
78
|
+
lambda { @slave.write_single_coil(501, true) }.should raise_error(IllegalDataAddress)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Preset single register
|
82
|
+
it "should preset single register" do
|
83
|
+
@slave.write_single_register(4, 0x0AA0).should == @slave
|
84
|
+
@slave.read_holding_registers(4, 1).should == [0x0AA0]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should raise exception if illegal data address" do
|
88
|
+
lambda { @slave.write_single_register(501, 0x0AA0) }.should raise_error(IllegalDataAddress)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Force multiple coils
|
92
|
+
it "should force multiple coils" do
|
93
|
+
@slave.write_multiple_coils(4, [0,1,0,1]).should == @slave
|
94
|
+
@slave.read_coils(3, 5).should == [0,0,1,0,1]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should raise exception if illegal data address" do
|
98
|
+
lambda { @slave.write_multiple_coils(501, [1,0]) }.should raise_error(IllegalDataAddress)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Preset multiple registers
|
102
|
+
it "should preset multiple registers" do
|
103
|
+
@slave.write_multiple_registers(4, [1, 2, 3, 0xAACC]).should == @slave
|
104
|
+
@slave.read_holding_registers(3, 5).should == [0, 1, 2, 3, 0xAACC]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should raise exception if illegal data address" do
|
108
|
+
lambda { @slave.write_multiple_registers(501, [1, 2]) }.should raise_error(IllegalDataAddress)
|
109
|
+
end
|
110
|
+
|
111
|
+
after(:all) do
|
112
|
+
@cl.close unless @cl.closed?
|
113
|
+
@srv.stop
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
data/spec/ext_spec.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
|
3
|
+
describe Array do
|
4
|
+
before do
|
5
|
+
@arr = [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
|
6
|
+
@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]
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return string reprisent 16bit" do
|
10
|
+
@arr.pack_to_word.should == "\xcd\x6b\x5"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "fixed bug for divisible 8 data " do
|
14
|
+
([0] * 8).pack_to_word.should == "\x00"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should unpack to @test" do
|
18
|
+
"test".unpack_bits == @test
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should turn an array into 32b ints" do
|
22
|
+
[20342, 17344].to_32i.should == [1136676726]
|
23
|
+
[20342, 17344, 20342, 17344].to_32i.size.should == 2
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should turn an array into 32b floats" do
|
27
|
+
[20342, 17344].to_32f[0].should be_within(0.1).of(384.620788574219)
|
28
|
+
[20342, 17344, 20342, 17344].to_32f.size.should == 2
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should turn an array from 32b ints into 16b ints, big endian" do
|
32
|
+
[1136676726].from_32i.should == [20342, 17344]
|
33
|
+
[1136676726, 1136676725].from_32i.should == [20342, 17344, 20341, 17344]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should turn an array from 32b floats into 16b ints, big endian" do
|
37
|
+
[384.620788].from_32f.should == [20342, 17344]
|
38
|
+
[384.620788, 384.620788].from_32f.should == [20342, 17344, 20342, 17344]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should raise exception if uneven number of elements" do
|
42
|
+
lambda { [20342, 17344, 123].to_32f }.should raise_error(StandardError)
|
43
|
+
lambda { [20342, 17344, 123].to_32i }.should raise_error(StandardError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
include ModBus
|
3
|
+
|
4
|
+
describe TCPClient do
|
5
|
+
before(:each) do
|
6
|
+
@uid = 1
|
7
|
+
@sock = mock("Socket")
|
8
|
+
@adu = "\000\001\000\000\000\001\001"
|
9
|
+
|
10
|
+
TCPSocket.should_receive(:new).with('127.0.0.1', 1502).and_return(@sock)
|
11
|
+
@sock.stub!(:read).with(0).and_return('')
|
12
|
+
|
13
|
+
@slave = TCPClient.new('127.0.0.1', 1502).with_slave(@uid)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should log rec\send bytes' do
|
17
|
+
request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
|
18
|
+
mock_query(request,response)
|
19
|
+
@slave.debug = true
|
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
|
+
request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
|
27
|
+
mock_query(request,response)
|
28
|
+
@slave.query(request)
|
29
|
+
end
|
30
|
+
|
31
|
+
def mock_query(request, response)
|
32
|
+
@adu = @slave.transaction.next.to_word + "\x0\x0\x0\x9" + @uid.chr + request
|
33
|
+
@sock.should_receive(:write).with(@adu[0,4] + "\0\6" + @uid.chr + request)
|
34
|
+
@sock.should_receive(:read).with(7).and_return(@adu[0,7])
|
35
|
+
@sock.should_receive(:read).with(8).and_return(response)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
unless RUBY_PLATFORM == "java"
|
40
|
+
describe RTUClient do
|
41
|
+
before do
|
42
|
+
@sp = mock('Serial port')
|
43
|
+
SerialPort.should_receive(:new).with("/dev/port1", 9600, 7, 2, SerialPort::ODD).and_return(@sp)
|
44
|
+
|
45
|
+
@sp.stub!(:read_timeout=)
|
46
|
+
|
47
|
+
@slave = RTUClient.new("/dev/port1", 9600, :data_bits => 7, :stop_bits => 2, :parity => SerialPort::ODD).with_slave(1)
|
48
|
+
@slave.read_retries = 0
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should log rec\send bytes' do
|
52
|
+
request = "\x3\x0\x1\x0\x1"
|
53
|
+
@sp.should_receive(:write).with("\1#{request}\xd5\xca")
|
54
|
+
@sp.should_receive(:read).with(2).and_return("\x1\x3")
|
55
|
+
@sp.should_receive(:read).with(1).and_return("\x2")
|
56
|
+
@sp.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
|
57
|
+
|
58
|
+
@slave.debug = true
|
59
|
+
$stdout.should_receive(:puts).with("Tx (8 bytes): [01][03][00][01][00][01][d5][ca]")
|
60
|
+
$stdout.should_receive(:puts).with("Rx (7 bytes): [01][03][02][ff][ff][b9][f4]")
|
61
|
+
|
62
|
+
@slave.query(request).should == "\xff\xff"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/spec/proxy_spec.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
|
3
|
+
describe Array do
|
4
|
+
before do
|
5
|
+
@slave = mock('ModBus Slave')
|
6
|
+
@coil_proxy = ModBus::ReadWriteProxy.new(@slave, :coil)
|
7
|
+
@discrete_input_proxy = ModBus::ReadOnlyProxy.new(@slave, :discrete_input)
|
8
|
+
@holding_register_proxy = ModBus::ReadWriteProxy.new(@slave, :holding_register)
|
9
|
+
@input_register_proxy = ModBus::ReadOnlyProxy.new(@slave, :input_register)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Handle all of the coil methods
|
13
|
+
it "should call read_coil" do
|
14
|
+
@slave.should_receive(:read_coil).with(0, 1)
|
15
|
+
@coil_proxy[0]
|
16
|
+
end
|
17
|
+
it "should call read_coils" do
|
18
|
+
@slave.should_receive(:read_coils).with(0, 2)
|
19
|
+
@coil_proxy[0..1]
|
20
|
+
end
|
21
|
+
it "should call write_coil" do
|
22
|
+
@slave.should_receive(:write_coil).with(0, 1)
|
23
|
+
@coil_proxy[0] = 1
|
24
|
+
end
|
25
|
+
it "should call write_coils" do
|
26
|
+
@slave.should_receive(:write_coils).with(0, [0, 0])
|
27
|
+
@coil_proxy[0..1] = [0, 0]
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Discrete input tests
|
32
|
+
it "should call read_discrete_input" do
|
33
|
+
@slave.should_receive(:read_discrete_input).with(0, 1)
|
34
|
+
@discrete_input_proxy[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should call read_discrete_inputs" do
|
38
|
+
@slave.should_receive(:read_discrete_inputs).with(0, 2)
|
39
|
+
@discrete_input_proxy[0..1]
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# Holding Register Tess
|
44
|
+
it "should call read_holding_register" do
|
45
|
+
@slave.should_receive(:read_holding_register).with(0, 1)
|
46
|
+
@holding_register_proxy[0]
|
47
|
+
end
|
48
|
+
it "should call read_holding_registers" do
|
49
|
+
@slave.should_receive(:read_holding_registers).with(0, 2)
|
50
|
+
@holding_register_proxy[0..1]
|
51
|
+
end
|
52
|
+
it "should call write_holding_register" do
|
53
|
+
@slave.should_receive(:write_holding_register).with(0, 1)
|
54
|
+
@holding_register_proxy[0] = 1
|
55
|
+
end
|
56
|
+
it "should call write_holding_registers" do
|
57
|
+
@slave.should_receive(:write_holding_registers).with(0, [0, 0])
|
58
|
+
@holding_register_proxy[0..1] = [0, 0]
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Input Register Tests
|
63
|
+
it "should call read_discrete_input" do
|
64
|
+
@slave.should_receive(:read_input_register).with(0, 1)
|
65
|
+
@input_register_proxy[0]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should call read_discrete_inputs" do
|
69
|
+
@slave.should_receive(:read_input_registers).with(0, 2)
|
70
|
+
@input_register_proxy[0..1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
include ModBus
|
3
|
+
|
4
|
+
#Use public wrap method
|
5
|
+
class Client
|
6
|
+
include RTU
|
7
|
+
def test_read_method(msg)
|
8
|
+
io = TestIO.new(msg)
|
9
|
+
read_rtu_response(io)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestIO
|
15
|
+
def initialize(msg)
|
16
|
+
@msg = msg
|
17
|
+
end
|
18
|
+
|
19
|
+
def read(num)
|
20
|
+
result = @msg[0,num]
|
21
|
+
@msg = @msg[num..-1]
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#read_rtu_response" do
|
27
|
+
before do
|
28
|
+
@cl_mb = Client.new
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should read response for 'read coils'" do
|
32
|
+
resp = make_resp("\x1\x3\xcd\x6b\x05")
|
33
|
+
@cl_mb.test_read_method(resp).should == resp
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should read response for 'read discrete inputs'" do
|
37
|
+
resp = make_resp("\x2\x3\xac\xdb\x35")
|
38
|
+
@cl_mb.test_read_method(resp).should == resp
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should read response for 'read holding registers'" do
|
42
|
+
resp = make_resp("\x3\x6\x2\x2b\x0\x0\x0\x64")
|
43
|
+
@cl_mb.test_read_method(resp).should == resp
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should read response for 'read input registers'" do
|
47
|
+
resp = make_resp("\x4\x2\x0\xa")
|
48
|
+
@cl_mb.test_read_method(resp).should == resp
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should read response for 'write single coil'" do
|
52
|
+
resp = make_resp("\x5\x0\xac\xff\x0")
|
53
|
+
@cl_mb.test_read_method(resp).should == resp
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should read response for 'write single register'" do
|
57
|
+
resp = make_resp("\x6\x0\x1\x0\x3")
|
58
|
+
@cl_mb.test_read_method(resp).should == resp
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should read response for 'write multiple coils'" do
|
62
|
+
resp = make_resp("\xf\x0\x13\x0\xa")
|
63
|
+
@cl_mb.test_read_method(resp).should == resp
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should read response for 'write multiple registers'" do
|
67
|
+
resp = make_resp("\x10\x0\x1\x0\x2")
|
68
|
+
@cl_mb.test_read_method(resp).should == resp
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should read response 'mask write register'" do
|
72
|
+
resp = make_resp("\x16\x0\x4\x0\xf2\x0\x25")
|
73
|
+
@cl_mb.test_read_method(resp).should == resp
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should read exception codes" do
|
77
|
+
resp = make_resp("\x84\x3")
|
78
|
+
@cl_mb.test_read_method(resp).should == resp
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should raise exception if function is illegal" do
|
82
|
+
resp = make_resp("\xff\x0\x1\x0\x2").should raise_error {
|
83
|
+
ModBus::Errors::IllegalFunction
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def make_resp(msg)
|
88
|
+
"\x1" + msg + "\x2\x2" # slave + msg + mock_crc
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
include ModBus
|
3
|
+
|
4
|
+
describe RTUClient do
|
5
|
+
before do
|
6
|
+
@sp = mock('Serial port')
|
7
|
+
SerialPort.should_receive(:new).with("/dev/port1", 9600, 8, 1, 0).and_return(@sp)
|
8
|
+
@sp.stub!(:read_timeout=)
|
9
|
+
@sp.stub!(:read)
|
10
|
+
|
11
|
+
@cl = RTUClient.new("/dev/port1", 9600, :data_bits => 8, :stop_bits => 1, :parity => SerialPort::NONE)
|
12
|
+
@slave = @cl.with_slave(1)
|
13
|
+
@slave.read_retries = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should ignore frame with other UID" do
|
17
|
+
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
18
|
+
@sp.should_receive(:write).with("\1#{request}\xA6\x31")
|
19
|
+
@sp.should_receive(:read).with(2).and_return("\x2\x10")
|
20
|
+
@sp.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
21
|
+
lambda {@slave.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should ignored frame with incorrect CRC" do
|
25
|
+
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
26
|
+
@sp.should_receive(:write).with("\1#{request}\xA6\x31")
|
27
|
+
@sp.should_receive(:read).with(2).and_return("\x2\x10")
|
28
|
+
@sp.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
29
|
+
lambda {@slave.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return value of registers"do
|
33
|
+
request = "\x3\x0\x1\x0\x1"
|
34
|
+
@sp.should_receive(:write).with("\1#{request}\xd5\xca")
|
35
|
+
@sp.should_receive(:read).with(2).and_return("\x1\x3")
|
36
|
+
@sp.should_receive(:read).with(1).and_return("\x2")
|
37
|
+
@sp.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
|
38
|
+
@slave.query(request).should == "\xff\xff"
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should sugar connect method' do
|
42
|
+
port, baud = "/dev/port1", 4800
|
43
|
+
SerialPort.should_receive(:new).with(port, baud, 8, 1, SerialPort::NONE).and_return(@sp)
|
44
|
+
@sp.should_receive(:closed?).and_return(false)
|
45
|
+
@sp.should_receive(:close)
|
46
|
+
RTUClient.connect(port, baud) do |cl|
|
47
|
+
cl.port.should == port
|
48
|
+
cl.baud.should == baud
|
49
|
+
cl.data_bits.should == 8
|
50
|
+
cl.stop_bits.should == 1
|
51
|
+
cl.parity.should == SerialPort::NONE
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should have closed? method' do
|
56
|
+
@sp.should_receive(:closed?).and_return(false)
|
57
|
+
@cl.closed?.should == false
|
58
|
+
|
59
|
+
@sp.should_receive(:closed?).and_return(false)
|
60
|
+
@sp.should_receive(:close)
|
61
|
+
|
62
|
+
@cl.close
|
63
|
+
|
64
|
+
@sp.should_receive(:closed?).and_return(true)
|
65
|
+
@cl.closed?.should == true
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should give slave object in block' do
|
69
|
+
@cl.with_slave(1) do |slave|
|
70
|
+
slave.uid = 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
include ModBus
|
3
|
+
|
4
|
+
describe RTUServer do
|
5
|
+
before do
|
6
|
+
@sp = mock "SerialPort"
|
7
|
+
SerialPort.should_receive(:new).with('/dev/ttyS0', 4800, 7, 2, SerialPort::NONE).and_return(@sp)
|
8
|
+
@sp.stub!(:read_timeout=)
|
9
|
+
|
10
|
+
@server = RTUServer.new('/dev/ttyS0', 4800, 1, :data_bits => 7, :stop_bits => 2)
|
11
|
+
@server.coils = [1,0,1,1]
|
12
|
+
@server.discrete_inputs = [1,1,0,0]
|
13
|
+
@server.holding_registers = [1,2,3,4]
|
14
|
+
@server.input_registers = [1,2,3,4]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be valid initialized " do
|
18
|
+
@server.coils.should == [1,0,1,1]
|
19
|
+
@server.discrete_inputs.should == [1,1,0,0]
|
20
|
+
@server.holding_registers.should == [1,2,3,4]
|
21
|
+
@server.input_registers.should == [1,2,3,4]
|
22
|
+
|
23
|
+
@server.port.should == '/dev/ttyS0'
|
24
|
+
@server.baud.should == 4800
|
25
|
+
@server.data_bits.should == 7
|
26
|
+
@server.stop_bits.should == 2
|
27
|
+
@server.parity.should == SerialPort::NONE
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
include ModBus
|
3
|
+
|
4
|
+
describe RTUViaTCPClient do
|
5
|
+
describe "method 'query'" do
|
6
|
+
before do
|
7
|
+
@sock = mock('Socked')
|
8
|
+
TCPSocket.should_receive(:new).with("127.0.0.1", 10002).and_return(@sock)
|
9
|
+
@sock.stub!(:read_timeout=)
|
10
|
+
@sock.stub!(:read)
|
11
|
+
|
12
|
+
@cl = RTUViaTCPClient.new("127.0.0.1")
|
13
|
+
@slave = @cl.with_slave(1)
|
14
|
+
@slave.read_retries = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should ignore frame with other UID" do
|
18
|
+
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
19
|
+
@sock.should_receive(:write).with("\1#{request}\xA6\x31")
|
20
|
+
@sock.should_receive(:read).with(2).and_return("\x2\x10")
|
21
|
+
@sock.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
22
|
+
lambda {@slave.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should ignored frame with incorrect CRC" do
|
26
|
+
request = "\x10\x0\x1\x0\x1\x2\xff\xff"
|
27
|
+
@sock.should_receive(:write).with("\1#{request}\xA6\x31")
|
28
|
+
@sock.should_receive(:read).with(2).and_return("\x2\x10")
|
29
|
+
@sock.should_receive(:read).with(6).and_return("\x0\x1\x0\x1\x1C\x08")
|
30
|
+
lambda {@slave.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return value of registers"do
|
34
|
+
request = "\x3\x0\x1\x0\x1"
|
35
|
+
@sock.should_receive(:write).with("\1#{request}\xd5\xca")
|
36
|
+
@sock.should_receive(:read).with(2).and_return("\x1\x3")
|
37
|
+
@sock.should_receive(:read).with(1).and_return("\x2")
|
38
|
+
@sock.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
|
39
|
+
@slave.query(request).should == "\xff\xff"
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should sugar connect method' do
|
43
|
+
ipaddr, port = '127.0.0.1', 502
|
44
|
+
TCPSocket.should_receive(:new).with(ipaddr, port).and_return(@sock)
|
45
|
+
@sock.should_receive(:closed?).and_return(false)
|
46
|
+
@sock.should_receive(:close)
|
47
|
+
RTUViaTCPClient.connect(ipaddr, port) do |cl|
|
48
|
+
cl.ipaddr.should == ipaddr
|
49
|
+
cl.port.should == port
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should have closed? method' do
|
54
|
+
@sock.should_receive(:closed?).and_return(false)
|
55
|
+
@cl.closed?.should == false
|
56
|
+
|
57
|
+
@sock.should_receive(:closed?).and_return(false)
|
58
|
+
@sock.should_receive(:close)
|
59
|
+
|
60
|
+
@cl.close
|
61
|
+
|
62
|
+
@sock.should_receive(:closed?).and_return(true)
|
63
|
+
@cl.closed?.should == true
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should give slave object in block' do
|
67
|
+
@cl.with_slave(1) do |slave|
|
68
|
+
slave.uid = 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should tune connection timeout" do
|
74
|
+
timeout(0.5) do
|
75
|
+
lambda { RTUViaTCPClient.new('81.123.231.11', 1999, :connect_timeout => 0.1) }.should raise_error(ModBusTimeout)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/spec/slave_spec.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rmodbus'
|
2
|
+
include ModBus
|
3
|
+
|
4
|
+
describe Slave do
|
5
|
+
before do
|
6
|
+
@slave = Client.new.with_slave(1)
|
7
|
+
|
8
|
+
@slave.stub!(:query).and_return('')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should support function 'read coils'" do
|
12
|
+
@slave.should_receive(:query).with("\x1\x0\x13\x0\x13").and_return("\xcd\x6b\x5")
|
13
|
+
@slave.read_coils(0x13,0x13).should == [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should support function 'read discrete inputs'" do
|
17
|
+
@slave.should_receive(:query).with("\x2\x0\xc4\x0\x16").and_return("\xac\xdb\x35")
|
18
|
+
@slave.read_discrete_inputs(0xc4,0x16).should == [0,0,1,1, 0,1,0,1, 1,1,0,1, 1,0,1,1, 1,0,1,0, 1,1]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should support function 'read holding registers'" do
|
22
|
+
@slave.should_receive(:query).with("\x3\x0\x6b\x0\x3").and_return("\x2\x2b\x0\x0\x0\x64")
|
23
|
+
@slave.read_holding_registers(0x6b,0x3).should == [0x022b, 0x0000, 0x0064]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should support function 'read input registers'" do
|
27
|
+
@slave.should_receive(:query).with("\x4\x0\x8\x0\x1").and_return("\x0\xa")
|
28
|
+
@slave.read_input_registers(0x8,0x1).should == [0x000a]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should support function 'write single coil'" do
|
32
|
+
@slave.should_receive(:query).with("\x5\x0\xac\xff\x0").and_return("\xac\xff\x00")
|
33
|
+
@slave.write_single_coil(0xac,0x1).should == @slave
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should support function 'write single register'" do
|
37
|
+
@slave.should_receive(:query).with("\x6\x0\x1\x0\x3").and_return("\x1\x0\x3")
|
38
|
+
@slave.write_single_register(0x1,0x3).should == @slave
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should support function 'write multiple coils'" do
|
42
|
+
@slave.should_receive(:query).with("\xf\x0\x13\x0\xa\x2\xcd\x1").and_return("\x13\x0\xa")
|
43
|
+
@slave.write_multiple_coils(0x13,[1,0,1,1, 0,0,1,1, 1,0]).should == @slave
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should support function 'write multiple registers'" do
|
47
|
+
@slave.should_receive(:query).with("\x10\x0\x1\x0\x3\x6\x0\xa\x1\x2\xf\xf").and_return("\x1\x0\x3")
|
48
|
+
@slave.write_multiple_registers(0x1,[0x000a,0x0102, 0xf0f]).should == @slave
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should support function 'mask write register'" do
|
52
|
+
@slave.should_receive(:query).with("\x16\x0\x4\x0\xf2\x0\2").and_return("\x4\x0\xf2\x0\x2")
|
53
|
+
@slave.mask_write_register(0x4, 0xf2, 0x2).should == @slave
|
54
|
+
end
|
55
|
+
end
|