rmodbus 0.2 → 0.2.1

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