rmodbus 0.3.1 → 0.4.0

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