rmodbus 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,21 +11,30 @@
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
- require 'rmodbus/exceptions'
14
+ require 'rmodbus/parsers'
15
15
  require 'gserver'
16
16
 
17
17
 
18
18
  module ModBus
19
-
20
19
  class TCPServer < GServer
20
+ include Parsers
21
21
 
22
- attr_accessor :coils, :discret_inputs, :holding_registers, :input_registers
22
+ attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers
23
23
 
24
- Funcs = [1,2,3,4,5,6,15,16]
25
-
24
+ def discret_inputs
25
+ warn "[DEPRECATION] `discret_inputs` is deprecated. Please use `discrete_inputs` instead."
26
+ @discrete_inputs
27
+ end
28
+
29
+ def discret_inputs=(val)
30
+ warn "[DEPRECATION] `discret_inputs=` is deprecated. Please use `discrete_inputs=` instead."
31
+ @discrete_inputs=val
32
+ end
33
+
34
+
26
35
  def initialize(port = 502, uid = 1)
27
36
  @coils = []
28
- @discret_inputs = []
37
+ @discrete_inputs = []
29
38
  @holding_registers =[]
30
39
  @input_registers = []
31
40
  @uid = uid
@@ -44,119 +53,10 @@ module ModBus
44
53
  len = req[4,2].unpack('n')[0]
45
54
  req = io.read(len - 1)
46
55
 
47
- func = req.getbyte(0)
56
+ pdu = exec_req(req, @coils, @discrete_inputs, @holding_registers, @input_registers)
48
57
 
49
- unless Funcs.include?(func)
50
- param = { :err => 1 }
51
- end
52
-
53
- case func
54
- when 1
55
- param = parse_read_func(req, @coils)
56
- if param[:err] == 0
57
- val = @coils[param[:addr],param[:quant]].pack_to_word
58
- res = func.chr + val.size.chr + val
59
- end
60
- when 2
61
- param = parse_read_func(req, @discret_inputs)
62
- if param[:err] == 0
63
- val = @discret_inputs[param[:addr],param[:quant]].pack_to_word
64
- res = func.chr + val.size.chr + val
65
- end
66
- when 3
67
- param = parse_read_func(req, @holding_registers)
68
- if param[:err] == 0
69
- res = func.chr + (param[:quant] * 2).chr + @holding_registers[param[:addr],param[:quant]].pack('n*')
70
- end
71
- when 4
72
- param = parse_read_func(req, @input_registers)
73
- if param[:err] == 0
74
- res = func.chr + (param[:quant] * 2).chr + @input_registers[param[:addr],param[:quant]].pack('n*')
75
- end
76
- when 5
77
- param = parse_write_coil_func(req)
78
- if param[:err] == 0
79
- @coils[param[:addr]] = param[:val]
80
- res = func.chr + req
81
- end
82
- when 6
83
- param = parse_write_register_func(req)
84
- if param[:err] == 0
85
- @holding_registers[param[:addr]] = param[:val]
86
- res = func.chr + req
87
- end
88
- when 15
89
- param = parse_write_multiple_coils_func(req)
90
- if param[:err] == 0
91
- @coils[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
92
- res = func.chr + req
93
- end
94
- when 16
95
- param = parse_write_multiple_registers_func(req)
96
- if param[:err] == 0
97
- @holding_registers[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
98
- res = func.chr + req
99
- end
100
- end
101
- if param[:err] == 0
102
- resp = tr + "\0\0" + (res.size + 1).to_word + @uid.chr + res
103
- else
104
- resp = tr + "\0\0\0\3" + @uid.chr + (func | 0x80).chr + param[:err].chr
105
- end
106
- io.write resp
107
- end
58
+ io.write tr + "\0\0" + (pdu.size + 1).to_word + @uid.chr + pdu
108
59
  end
109
-
110
- private
111
-
112
- def parse_read_func(req, field)
113
- quant = req[3,2].unpack('n')[0]
114
-
115
- return { :err => 3} unless quant <= 0x7d
116
-
117
- addr = req[1,2].unpack('n')[0]
118
- return { :err => 2 } unless addr + quant <= field.size
119
-
120
- return { :err => 0, :quant => quant, :addr => addr }
121
- end
122
-
123
- def parse_write_coil_func(req)
124
- addr = req[1,2].unpack('n')[0]
125
- return { :err => 2 } unless addr <= @coils.size
126
-
127
- val = req[3,2].unpack('n')[0]
128
- return { :err => 3 } unless val == 0 or val == 0xff00
129
-
130
- val = 1 if val == 0xff00
131
- return { :err => 0, :addr => addr, :val => val }
132
60
  end
133
-
134
- def parse_write_register_func(req)
135
- addr = req[1,2].unpack('n')[0]
136
- return { :err => 2 } unless addr <= @coils.size
137
-
138
- val = req[3,2].unpack('n')[0]
139
-
140
- return { :err => 0, :addr => addr, :val => val }
141
- end
142
-
143
- def parse_write_multiple_coils_func(req)
144
- param = parse_read_func(req, @coils)
145
-
146
- if param[:err] == 0
147
- param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant]].unpack_bits }
148
- end
149
- param
150
- end
151
-
152
- def parse_write_multiple_registers_func(req)
153
- param = parse_read_func(req, @holding_registers)
154
-
155
- if param[:err] == 0
156
- param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant] * 2].unpack('n*')}
157
- end
158
- param
159
- end
160
-
161
- end
61
+ end
162
62
  end
data/lib/rmodbus.rb CHANGED
@@ -12,4 +12,5 @@
12
12
  require 'rmodbus/tcp_client'
13
13
  require 'rmodbus/tcp_server'
14
14
  require 'rmodbus/rtu_client'
15
+ require 'rmodbus/rtu_server'
15
16
 
@@ -0,0 +1,117 @@
1
+ require 'rmodbus'
2
+
3
+ include ModBus
4
+ include ModBus::Errors
5
+
6
+ describe ModBus::TCPClient do
7
+
8
+ before(:all) do
9
+ @srv = ModBus::TCPServer.new(1502, 1)
10
+ @srv.coils = [0] * 8
11
+ @srv.discrete_inputs = [0] * 8
12
+ @srv.holding_registers = [0] * 8
13
+ @srv.input_registers = [0] * 8
14
+ @srv.start
15
+
16
+ @cl =TCPClient.new('127.0.0.1', 1502, 1)
17
+ end
18
+
19
+ # Read coil status
20
+ it "should read coil status" do
21
+ @cl.read_coils(0, 4).should == [0] * 4
22
+ end
23
+
24
+ it "should raise exception if illegal data address" do
25
+ lambda { @cl.read_coils(501, 34) }.should raise_error(IllegalDataAddress)
26
+ end
27
+
28
+ it "should raise exception if too many data" do
29
+ lambda { @cl.read_coils(0, 0x07D1) }.should raise_error(IllegalDataValue)
30
+ end
31
+
32
+ # Read input status
33
+ it "should read discrete inputs" do
34
+ @cl.read_discrete_inputs(0, 4).should == [0] * 4
35
+ end
36
+
37
+ it "should raise exception if illegal data address" do
38
+ lambda { @cl.read_discrete_inputs(50, 23) }.should raise_error(IllegalDataAddress)
39
+ end
40
+
41
+ it "should raise exception if too many data" do
42
+ lambda { @cl.read_discrete_inputs(0, 0x07D1) }.should raise_error(IllegalDataValue)
43
+ end
44
+
45
+ # Read holding registers
46
+ it "should read discrete inputs" do
47
+ @cl.read_holding_registers(0, 4).should == [0, 0, 0, 0]
48
+ end
49
+
50
+ it "should raise exception if illegal data address" do
51
+ lambda { @cl.read_holding_registers(402, 99) }.should raise_error(IllegalDataAddress)
52
+ end
53
+
54
+
55
+ it "should raise exception if too many data" do
56
+ lambda { @cl.read_holding_registers(0, 0x007E) }.should raise_error(IllegalDataValue)
57
+ end
58
+
59
+ # Read input registers
60
+ it "should read discrete inputs" do
61
+ @cl.read_input_registers(0, 4).should == [0, 0, 0, 0]
62
+ end
63
+
64
+ it "should raise exception if illegal data address" do
65
+ lambda { @cl.read_input_registers(402, 9) }.should raise_error(IllegalDataAddress)
66
+ end
67
+
68
+ it "should raise exception if too many data" do
69
+ lambda { @cl.read_input_registers(0, 0x007E) }.should raise_error(IllegalDataValue)
70
+ end
71
+
72
+ # Force single coil
73
+ it "should force single coil" do
74
+ @cl.write_single_coil(4, 1).should == @cl
75
+ @cl.read_coils(4, 4).should == [1, 0, 0, 0]
76
+ end
77
+
78
+ it "should raise exception if illegal data address" do
79
+ lambda { @cl.write_single_coil(501, true) }.should raise_error(IllegalDataAddress)
80
+ end
81
+
82
+ # Preset single register
83
+ it "should preset single register" do
84
+ @cl.write_single_register(4, 0x0AA0).should == @cl
85
+ @cl.read_holding_registers(4, 1).should == [0x0AA0]
86
+ end
87
+
88
+ it "should raise exception if illegal data address" do
89
+ lambda { @cl.write_single_register(501, 0x0AA0) }.should raise_error(IllegalDataAddress)
90
+ end
91
+
92
+ # Force multiple coils
93
+ it "should force multiple coils" do
94
+ @cl.write_multiple_coils(4, [0,1,0,1]).should == @cl
95
+ @cl.read_coils(3, 5).should == [0,0,1,0,1]
96
+ end
97
+
98
+ it "should raise exception if illegal data address" do
99
+ lambda { @cl.write_multiple_coils(501, [1,0]) }.should raise_error(IllegalDataAddress)
100
+ end
101
+
102
+ # Preset multiple registers
103
+ it "should preset multiple registers" do
104
+ @cl.write_multiple_registers(4, [1, 2, 3, 0xAACC]).should == @cl
105
+ @cl.read_holding_registers(3, 5).should == [0, 1, 2, 3, 0xAACC]
106
+ end
107
+
108
+ it "should raise exception if illegal data address" do
109
+ lambda { @cl.write_multiple_registers(501, [1, 2]) }.should raise_error(IllegalDataAddress)
110
+ end
111
+
112
+ after(:all) do
113
+ @cl.close unless @cl.closed?
114
+ @srv.stop
115
+ end
116
+
117
+ end
data/spec/ext_spec.rb CHANGED
@@ -7,7 +7,11 @@ describe Array do
7
7
  end
8
8
 
9
9
  it "should return string reprisent 16bit" do
10
- @arr.pack_to_word == "\xcd\x6b\x5"
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"
11
15
  end
12
16
 
13
17
  end
@@ -0,0 +1,69 @@
1
+ require 'rmodbus'
2
+
3
+ include ModBus
4
+
5
+ describe TCPClient do
6
+
7
+ UID = 1
8
+
9
+ before(:each) do
10
+ @sock = mock("Socket")
11
+ @adu = "\000\001\000\000\000\001\001"
12
+
13
+ TCPSocket.should_receive(:new).with('127.0.0.1', 1502).and_return(@sock)
14
+ @sock.stub!(:read).with(0).and_return('')
15
+
16
+ @mb_client = TCPClient.new('127.0.0.1', 1502)
17
+ end
18
+
19
+ it 'should log rec\send bytes' do
20
+ request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
21
+ mock_query(request,response)
22
+ @mb_client.debug = true
23
+ STDOUT.should_receive("<<").with("Tx (12 bytes): [00][01][00][00][00][06][01][03][00][6b][00][03]\n")
24
+ STDOUT.should_receive("<<").with("Rx (15 bytes): [00][01][00][00][00][09][01][03][06][02][2b][00][00][00][64]\n")
25
+ @mb_client.query(request)
26
+ end
27
+
28
+ it "should don't logging if debug disable" do
29
+ request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
30
+ mock_query(request,response)
31
+ @mb_client.query(request)
32
+ end
33
+
34
+
35
+ def mock_query(request, response)
36
+ @adu = TCPClient.transaction.next.to_word + "\x0\x0\x0\x9" + UID.chr + request
37
+ @sock.should_receive(:write).with(@adu[0,4] + "\0\6" + UID.chr + request)
38
+ @sock.should_receive(:read).with(7).and_return(@adu[0,7])
39
+ @sock.should_receive(:read).with(8).and_return(response)
40
+ end
41
+
42
+ end
43
+
44
+ describe RTUClient do
45
+
46
+ before do
47
+ @sp = mock('Serial port')
48
+ SerialPort.should_receive(:new).with("/dev/port1", 9600, 7, 2, SerialPort::ODD).and_return(@sp)
49
+
50
+ @sp.stub!(:read_timeout=)
51
+
52
+ @mb_client = RTUClient.new("/dev/port1", 9600, 1, :data_bits => 7, :stop_bits => 2, :parity => SerialPort::ODD)
53
+ @mb_client.read_retries = 0
54
+ end
55
+
56
+ it 'should log rec\send bytes' do
57
+ request = "\x3\x0\x1\x0\x1"
58
+ @sp.should_receive(:write).with("\1#{request}\xd5\xca")
59
+ @sp.should_receive(:read).and_return("\x1\x3\x2\xff\xff\xb9\xf4")
60
+
61
+ @mb_client.debug = true
62
+ STDOUT.should_receive("<<").with("Tx (8 bytes): [01][03][00][01][00][01][d5][ca]\n")
63
+ STDOUT.should_receive("<<").with("Rx (7 bytes): [01][03][02][ff][ff][b9][f4]\n")
64
+
65
+ @mb_client.query(request).should == "\xff\xff"
66
+ end
67
+
68
+ end
69
+
@@ -9,33 +9,63 @@ include ModBus
9
9
  describe RTUClient do
10
10
 
11
11
  before do
12
- @port = mock('Serial port')
13
- SerialPort.should_receive(:new).with("/dev/port1", 9600).and_return(@port)
14
- @port.stub!(:read_timeout=)
15
- @mb_client = RTUClient.new("/dev/port1", 9600, 1)
12
+ @sp = mock('Serial port')
13
+ SerialPort.should_receive(:new).with("/dev/port1", 9600, 8, 1, 0).and_return(@sp)
14
+ @sp.stub!(:read_timeout=)
15
+
16
+ @mb_client = RTUClient.new("/dev/port1", 9600, 1,
17
+ :data_bits => 8, :stop_bits => 1, :parity => SerialPort::NONE)
16
18
  @mb_client.read_retries = 0
17
19
  end
18
20
 
19
21
  it "should ignore frame with other UID" do
20
22
  request = "\x10\x0\x1\x0\x1\x2\xff\xff"
21
- @port.should_receive(:write).with("\1#{request}\xA6\x31")
22
- @port.should_receive(:read).and_return("\x2\x10\x0\x1\x0\x1\x1C\x08")
23
+ @sp.should_receive(:write).with("\1#{request}\xA6\x31")
24
+ @sp.should_receive(:read).and_return("\x2\x10\x0\x1\x0\x1\x1C\x08")
23
25
  lambda {@mb_client.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
24
26
  end
25
27
 
26
28
  it "should ignored frame with incorrect CRC" do
27
29
  request = "\x10\x0\x1\x0\x1\x2\xff\xff"
28
- @port.should_receive(:write).with("\1#{request}\xA6\x31")
29
- @port.should_receive(:read).and_return("\x1\x10\x0\x1\x0\x1\x1C\x08")
30
+ @sp.should_receive(:write).with("\1#{request}\xA6\x31")
31
+ @sp.should_receive(:read).and_return("\x1\x10\x0\x1\x0\x1\x1C\x08")
30
32
  lambda {@mb_client.query(request)}.should raise_error(ModBus::Errors::ModBusTimeout)
31
33
  end
32
34
 
33
35
  it "should return value of registers"do
34
36
  request = "\x3\x0\x1\x0\x1"
35
- @port.should_receive(:write).with("\1#{request}\xd5\xca")
36
- @port.should_receive(:read).and_return("\x1\x3\x2\xff\xff\xb9\xf4")
37
+ @sp.should_receive(:write).with("\1#{request}\xd5\xca")
38
+ @sp.should_receive(:read).and_return("\x1\x3\x2\xff\xff\xb9\xf4")
37
39
  @mb_client.query(request).should == "\xff\xff"
38
40
  end
39
41
 
42
+ it 'should sugar connect method' do
43
+ port, baud, slave = "/dev/port1", 4800, 3
44
+ SerialPort.should_receive(:new).with(port, baud, 8, 1, SerialPort::NONE).and_return(@sp)
45
+ @sp.should_receive(:closed?).and_return(false)
46
+ @sp.should_receive(:close)
47
+ RTUClient.connect(port, baud, slave) do |cl|
48
+ cl.port.should == port
49
+ cl.baud.should == baud
50
+ cl.slave.should == slave
51
+ cl.data_bits.should == 8
52
+ cl.stop_bits.should == 1
53
+ cl.parity.should == SerialPort::NONE
54
+ end
55
+ end
56
+
57
+ it 'should have closed? method' do
58
+ @sp.should_receive(:closed?).and_return(false)
59
+ @mb_client.closed?.should == false
60
+
61
+ @sp.should_receive(:closed?).and_return(false)
62
+ @sp.should_receive(:close)
63
+
64
+ @mb_client.close
65
+
66
+ @sp.should_receive(:closed?).and_return(true)
67
+ @mb_client.closed?.should == true
68
+ end
69
+
40
70
  end
41
71
 
@@ -0,0 +1,31 @@
1
+ require 'rmodbus'
2
+
3
+ include ModBus
4
+
5
+ describe RTUServer do
6
+ before do
7
+ @sp = mock "SerialPort"
8
+ SerialPort.should_receive(:new).with('/dev/ttyS0', 4800, 7, 2, SerialPort::NONE).and_return(@sp)
9
+ @sp.stub!(:read_timeout=)
10
+
11
+ @server = RTUServer.new('/dev/ttyS0', 4800, 1, :data_bits => 7, :stop_bits => 2)
12
+ @server.coils = [1,0,1,1]
13
+ @server.discrete_inputs = [1,1,0,0]
14
+ @server.holding_registers = [1,2,3,4]
15
+ @server.input_registers = [1,2,3,4]
16
+ end
17
+
18
+ it "should be valid initialized " do
19
+ @server.coils.should == [1,0,1,1]
20
+ @server.discrete_inputs.should == [1,1,0,0]
21
+ @server.holding_registers.should == [1,2,3,4]
22
+ @server.input_registers.should == [1,2,3,4]
23
+
24
+ @server.port.should == '/dev/ttyS0'
25
+ @server.baud.should == 4800
26
+ @server.data_bits.should == 7
27
+ @server.stop_bits.should == 2
28
+ @server.parity.should == SerialPort::NONE
29
+ end
30
+
31
+ end