rmodbus 0.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,11 +27,11 @@ module ModBus
27
27
 
28
28
  # Connect with a ModBus server
29
29
  def initialize(ipaddr, port = 502, slaveaddr = 1)
30
-
30
+ @ipaddr, @port = ipaddr, port
31
31
  tried = 0
32
32
  begin
33
33
  timeout(1, ModBusTimeout) do
34
- @sock = TCPSocket.new(ipaddr, port)
34
+ @sock = TCPSocket.new(@ipaddr, @port)
35
35
  end
36
36
  rescue ModBusTimeout => err
37
37
  tried += 1
@@ -44,7 +44,6 @@ module ModBus
44
44
  def close
45
45
  @sock.close unless @sock.closed?
46
46
  end
47
-
48
47
 
49
48
  def self.transaction
50
49
  @@transaction
@@ -56,7 +55,7 @@ module ModBus
56
55
  @sock.write @@transaction.to_bytes + "\0\0" + (pdu.size + 1).to_bytes + @slave.chr + pdu
57
56
  end
58
57
 
59
- def read_pdu
58
+ def read_pdu
60
59
  header = @sock.read(7)
61
60
  if header
62
61
  tin = header[0,2].to_int16
@@ -21,7 +21,9 @@ module ModBus
21
21
 
22
22
  attr_accessor :coils, :discret_inputs, :holding_registers, :input_registers
23
23
 
24
- @@funcs = [1,2,3,4,5,6,15,16]
24
+
25
+
26
+ Funcs = [1,2,3,4,5,6,15,16]
25
27
 
26
28
  def initialize(port = 502, uid = 1)
27
29
  @coils = []
@@ -33,74 +35,89 @@ module ModBus
33
35
  end
34
36
 
35
37
  def serve(io)
36
- req = io.read(7)
37
- if req[2,2] != "\x00\x00" or req[6].to_i != @uid
38
- io.close
39
- return
40
- end
41
-
42
- tr = req[0,2]
43
- len = req[4,2].to_int16
44
- req = io.read(len - 1)
45
- func = req[0].to_i
46
-
47
- unless @@funcs.include?(func)
48
- param = { :err => 1 }
49
- end
50
-
51
- case func
52
- when 1
53
- param = parse_read_func(req, @coils)
54
- if param[:err] == 0
55
- val = @coils[param[:addr],param[:quant]].bits_to_bytes
56
- res = func.chr + val.size.chr + val
38
+ loop do
39
+ req = io.read(7)
40
+ if RUBY_VERSION.to_f == 1.9
41
+ if req[2,2] != "\x00\x00" or req.getbyte(6) != @uid
42
+ io.close
43
+ break
57
44
  end
58
- when 2
59
- param = parse_read_func(req, @discret_inputs)
60
- if param[:err] == 0
61
- val = @discret_inputs[param[:addr],param[:quant]].bits_to_bytes
62
- res = func.chr + val.size.chr + val
63
- end
64
- when 3
65
- param = parse_read_func(req, @holding_registers)
66
- if param[:err] == 0
67
- res = func.chr + (param[:quant] * 2).chr + @holding_registers[param[:addr],param[:quant]].to_ints16
68
- end
69
- when 4
70
- param = parse_read_func(req, @input_registers)
71
- if param[:err] == 0
72
- res = func.chr + (param[:quant] * 2).chr + @input_registers[param[:addr],param[:quant]].to_ints16
73
- end
74
- when 5
75
- param = parse_write_coil_func(req)
76
- if param[:err] == 0
77
- @coils[param[:addr]] = param[:val]
78
- res = func.chr + req
79
- end
80
- when 6
81
- param = parse_write_register_func(req)
82
- if param[:err] == 0
83
- @holding_registers[param[:addr]] = param[:val]
84
- res = func.chr + req
85
- end
86
- when 15
87
- param = parse_write_multiple_coils_func(req)
88
- if param[:err] == 0
89
- @coils[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
90
- res = func.chr + req
91
- end
92
- when 16
93
- param = parse_write_multiple_registers_func(req)
94
- if param[:err] == 0
95
- @holding_registers[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
96
- res = func.chr + req
45
+ else
46
+ if req[2,2] != "\x00\x00" or req[6].to_i != @uid
47
+ io.close
48
+ break
97
49
  end
50
+ end
51
+
52
+ tr = req[0,2]
53
+ len = req[4,2].to_int16
54
+ req = io.read(len - 1)
55
+
56
+ if RUBY_VERSION.to_f == 1.9
57
+ func = req.getbyte(0)
58
+ else
59
+ func = req[0].to_i
60
+ end
61
+
62
+ unless Funcs.include?(func)
63
+ param = { :err => 1 }
64
+ end
65
+
66
+ case func
67
+ when 1
68
+ param = parse_read_func(req, @coils)
69
+ if param[:err] == 0
70
+ val = @coils[param[:addr],param[:quant]].bits_to_bytes
71
+ res = func.chr + val.size.chr + val
72
+ end
73
+ when 2
74
+ param = parse_read_func(req, @discret_inputs)
75
+ if param[:err] == 0
76
+ val = @discret_inputs[param[:addr],param[:quant]].bits_to_bytes
77
+ res = func.chr + val.size.chr + val
78
+ end
79
+ when 3
80
+ param = parse_read_func(req, @holding_registers)
81
+ if param[:err] == 0
82
+ res = func.chr + (param[:quant] * 2).chr + @holding_registers[param[:addr],param[:quant]].to_ints16
83
+ end
84
+ when 4
85
+ param = parse_read_func(req, @input_registers)
86
+ if param[:err] == 0
87
+ res = func.chr + (param[:quant] * 2).chr + @input_registers[param[:addr],param[:quant]].to_ints16
88
+ end
89
+ when 5
90
+ param = parse_write_coil_func(req)
91
+ if param[:err] == 0
92
+ @coils[param[:addr]] = param[:val]
93
+ res = func.chr + req
94
+ end
95
+ when 6
96
+ param = parse_write_register_func(req)
97
+ if param[:err] == 0
98
+ @holding_registers[param[:addr]] = param[:val]
99
+ res = func.chr + req
100
+ end
101
+ when 15
102
+ param = parse_write_multiple_coils_func(req)
103
+ if param[:err] == 0
104
+ @coils[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
105
+ res = func.chr + req
106
+ end
107
+ when 16
108
+ param = parse_write_multiple_registers_func(req)
109
+ if param[:err] == 0
110
+ @holding_registers[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
111
+ res = func.chr + req
112
+ end
113
+ end
114
+ if param[:err] == 0
115
+ resp = tr + "\0\0" + (res.size + 1).to_bytes + @uid.chr + res
116
+ else
117
+ resp = tr + "\0\0\0\3" + @uid.chr + (func | 0x80).chr + param[:err].chr
118
+ end
119
+ io.write resp
98
120
  end
99
- if param[:err] == 0
100
- io.write tr + "\0\0" + (res.size + 1).to_bytes + @uid.chr + res
101
- else
102
- io.write tr + "\0\0\0\3" + @uid.chr + (func | 0x80).chr + param[:err].chr
103
- end
104
121
  end
105
122
 
106
123
  private
@@ -0,0 +1,57 @@
1
+ require 'rmodbus/client'
2
+
3
+ include ModBus
4
+
5
+ describe Client do
6
+
7
+ before do
8
+ @cl_mb = Client.new
9
+ @cl_mb.stub!(:query).and_return('')
10
+ end
11
+
12
+ it "should support function 'read coils'" do
13
+ @cl_mb.should_receive(:query).with("\x1\x0\x13\x0\x13").and_return("\xcd\x6b\x5")
14
+ @cl_mb.read_coils(0x13,0x13).should == [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
15
+ end
16
+
17
+ it "should support function 'read discrete inputs'" do
18
+ @cl_mb.should_receive(:query).with("\x2\x0\xc4\x0\x16").and_return("\xac\xdb\x35")
19
+ @cl_mb.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]
20
+ end
21
+
22
+ it "should support function 'read holding registers'" do
23
+ @cl_mb.should_receive(:query).with("\x3\x0\x6b\x0\x3").and_return("\x2\x2b\x0\x0\x0\x64")
24
+ @cl_mb.read_holding_registers(0x6b,0x3).should == [0x022b, 0x0000, 0x0064]
25
+ end
26
+
27
+ it "should support function 'read input registers'" do
28
+ @cl_mb.should_receive(:query).with("\x4\x0\x8\x0\x1").and_return("\x0\xa")
29
+ @cl_mb.read_input_registers(0x8,0x1).should == [0x000a]
30
+ end
31
+
32
+ it "should support function 'write single coil'" do
33
+ @cl_mb.should_receive(:query).with("\x5\x0\xac\xff\x0").and_return("\xac\xff\x00")
34
+ @cl_mb.write_single_coil(0xac,0x1).should == @cl_mb
35
+ end
36
+
37
+ it "should support function 'write single register'" do
38
+ @cl_mb.should_receive(:query).with("\x6\x0\x1\x0\x3").and_return("\x1\x0\x3")
39
+ @cl_mb.write_single_register(0x1,0x3).should == @cl_mb
40
+ end
41
+
42
+ it "should support function 'write multiple coils'" do
43
+ @cl_mb.should_receive(:query).with("\xf\x0\x13\x0\xa\x2\xcd\x1").and_return("\x13\x0\xa")
44
+ @cl_mb.write_multiple_coils(0x13,[1,0,1,1, 0,0,1,1, 1,0]).should == @cl_mb
45
+ end
46
+
47
+ it "should support function 'write multiple registers'" do
48
+ @cl_mb.should_receive(:query).with("\x10\x0\x1\x0\x3\x6\x0\xa\x1\x2\xf\xf").and_return("\x1\x0\x3")
49
+ @cl_mb.write_multiple_registers(0x1,[0x000a,0x0102, 0xf0f]).should == @cl_mb
50
+ end
51
+
52
+ it "should support function 'mask write register'" do
53
+ @cl_mb.should_receive(:query).with("\x16\x0\x4\x0\xf2\x0\2").and_return("\x4\x0\xf2\x0\x2")
54
+ @cl_mb.mask_write_register(0x4, 0xf2, 0x2).should == @cl_mb
55
+ end
56
+
57
+ end
@@ -0,0 +1,27 @@
1
+ require 'rmodbus'
2
+
3
+ describe Array do
4
+
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
+ end
8
+
9
+ it "should return string reprisent 16bit" do
10
+ @arr.bits_to_bytes.should == "\xcd\x6b\x5"
11
+ end
12
+
13
+ it "should return string reprisent 16ints" do
14
+ @arr = [1,2]
15
+ @arr.to_ints16 == "\x0\x1\x0\x2"
16
+ end
17
+
18
+ end
19
+
20
+ describe String do
21
+
22
+ it "should return array of int16" do
23
+ @str = "\x1\x2\x3\x4\x5\x6"
24
+ @str.to_array_int16.should == [0x102, 0x304, 0x506]
25
+ end
26
+
27
+ end
@@ -0,0 +1,48 @@
1
+ require 'rmodbus/tcp_client'
2
+
3
+ include ModBus
4
+
5
+ describe TCPClient, "method 'query'" do
6
+
7
+ UID = 1
8
+
9
+ before 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 send valid MBAP Header' do
20
+ @adu[0,2] = TCPClient.transaction.next.to_bytes
21
+ @sock.should_receive(:write).with(@adu)
22
+ @sock.should_receive(:read).with(7).and_return(@adu)
23
+ @mb_client.query('').should == nil
24
+ end
25
+
26
+ it 'should throw exception if get other transaction' do
27
+ @adu[0,2] = TCPClient.transaction.next.to_bytes
28
+ @sock.should_receive(:write).with(@adu)
29
+ @sock.should_receive(:read).with(7).and_return("\000\002\000\000\000\001" + UID.chr)
30
+ begin
31
+ @mb_client.query('').should == nil
32
+ rescue Exception => ex
33
+ ex.class.should == Errors::ModBusException
34
+ end
35
+ end
36
+
37
+ it 'should return only data from PDU' do
38
+ request = "\x3\x0\x6b\x0\x3"
39
+ response = "\x3\x6\x2\x2b\x0\x0\x0\x64"
40
+ @adu = TCPClient.transaction.next.to_bytes + "\x0\x0\x0\x9" + UID.chr + request
41
+ @sock.should_receive(:write).with(@adu[0,4] + "\0\6" + UID.chr + request)
42
+ @sock.should_receive(:read).with(7).and_return(@adu[0,7])
43
+ @sock.should_receive(:read).with(8).and_return(response)
44
+
45
+ @mb_client.query(request).should == response[2..-1]
46
+ end
47
+
48
+ end
@@ -0,0 +1,106 @@
1
+ require 'lib/rmodbus'
2
+
3
+ describe TCPServer do
4
+
5
+ before do
6
+ @server = ModBus::TCPServer.new(8502,1)
7
+ @server.coils = [1,0,1,1]
8
+ @server.discret_inputs = [1,1,0,0]
9
+ @server.holding_registers = [1,2,3,4]
10
+ @server.input_registers = [1,2,3,4]
11
+ @server.start
12
+ @client = ModBus::TCPClient.new('127.0.0.1', 8502, 1)
13
+ end
14
+
15
+ it "should silent if UID has mismatched" do
16
+ @client.close
17
+ client = ModBus::TCPClient.new('127.0.0.1', 8502, 2)
18
+ begin
19
+ client.read_coils(1,3)
20
+ rescue ModBus::Errors::ModBusException => ex
21
+ ex.message.should == "Server did not respond"
22
+ end
23
+ end
24
+
25
+ it "should silent if protocol identifer has mismatched" do
26
+ client = TCPSocket.new('127.0.0.1', 8502)
27
+ begin
28
+ client.write "\0\0\1\0\0\6\1"
29
+ rescue ModBus::Errors::ModBusException => ex
30
+ ex.message.should == "Server did not respond"
31
+ end
32
+ end
33
+
34
+ it "should send exception if function not supported" do
35
+ begin
36
+ @client.query('0x43')
37
+ rescue ModBus::Errors::IllegalFunction => ex
38
+ ex.message.should == "The function code received in the query is not an allowable action for the server"
39
+ end
40
+ end
41
+
42
+ it "should send exception if quanity of out more 0x7d" do
43
+ begin
44
+ @client.read_coils(0, 0x7e)
45
+ rescue ModBus::Errors::IllegalDataValue => ex
46
+ ex.message.should == "A value contained in the query data field is not an allowable value for server"
47
+ end
48
+ end
49
+
50
+ it "should send exception if addr not valid" do
51
+ begin
52
+ @client.read_coils(2, 8)
53
+ rescue ModBus::Errors::IllegalDataAddress => ex
54
+ ex.message.should == "The data address received in the query is not an allowable address for the server"
55
+ end
56
+ end
57
+
58
+ it "should supported function 'read coils'" do
59
+ @client.read_coils(0,3).should == @server.coils[0,3]
60
+ end
61
+
62
+ after do
63
+ @server.stop
64
+ end
65
+
66
+ it "should supported function 'read discrete inputs'" do
67
+ @client.read_discrete_inputs(1,3).should == @server.discret_inputs[1,3]
68
+ end
69
+
70
+ it "should supported function 'read holding registers'" do
71
+ @client.read_holding_registers(0,3).should == @server.holding_registers[0,3]
72
+ end
73
+
74
+ it "should supported function 'read input registers'" do
75
+ @client.read_input_registers(2,2).should == @server.input_registers[2,2]
76
+ end
77
+
78
+ it "should supported function 'write single coil'" do
79
+ @server.coils[3] = 0
80
+ @client.write_single_coil(3,1)
81
+ @server.coils[3].should == 1
82
+ end
83
+
84
+ it "should supported function 'write single register'" do
85
+ @server.holding_registers[3] = 25
86
+ @client.write_single_register(3,35)
87
+ @server.holding_registers[3].should == 35
88
+ end
89
+
90
+ it "should supported function 'write multiple coils'" do
91
+ @server.coils = [1,1,1,0, 0,0,0,0, 0,0,0,0, 0,1,1,1]
92
+ @client.write_multiple_coils(3, [1, 0,1,0,1, 0,1,0,1])
93
+ @server.coils.should == [1,1,1,1, 0,1,0,1, 0,1,0,1, 0,1,1,1]
94
+ end
95
+
96
+ it "should supported function 'write multiple registers'" do
97
+ @server.holding_registers = [1,2,3,4,5,6,7,8,9]
98
+ @client.write_multiple_registers(3,[1,2,3,4,5])
99
+ @server.holding_registers.should == [1,2,3,1,2,3,4,5,9]
100
+ end
101
+
102
+ after do
103
+ @client.close if @client
104
+ @server.stop if @server
105
+ end
106
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rmodbus
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.2"
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - A.Timin, J. Sanders
@@ -9,7 +9,7 @@ autorequire: rmodbus
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-30 00:00:00 +05:00
12
+ date: 2009-02-09 00:00:00 +05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,14 +25,17 @@ extra_rdoc_files:
25
25
  - LICENSE
26
26
  - CHANGES
27
27
  files:
28
- - lib/rmodbus/rtu_client.rb
29
28
  - lib/rmodbus/tcp_server.rb
30
29
  - lib/rmodbus/client.rb
31
30
  - lib/rmodbus/exceptions.rb
32
31
  - lib/rmodbus/tcp_client.rb
33
32
  - lib/rmodbus.rb
34
- - ext/extconf.rb
35
- - ext/serialport.c
33
+ - examples/add_new_function.rb
34
+ - examples/use_tcp_modbus.rb
35
+ - spec/client_spec.rb
36
+ - spec/tcp_client_spec.rb
37
+ - spec/tcp_server_spec.rb
38
+ - spec/ext_spec.rb
36
39
  - README
37
40
  - AUTHORS
38
41
  - LICENSE