rmodbus 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog ADDED
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ 2009-03-5 Timin Aleksey <atimin@gmail.com>
4
+ * lib/rmodbus/ext.rb: Added.
5
+ * lib/rmodbus/*.rb, spec/*_spec.rb: Have done
6
+ refactoring. Now using pack\unpack methods.
7
+ * CHANGES: Delete.
8
+ * ChageLog: Start new log of shanges.
9
+
10
+ 2009-03-6 Timin Aleksey <atimin@gmail.com>
11
+ * lib/rmodbus/rtu_client.rb, spec/rtu_client_spec.rb: Have completed to
12
+ design of ModBus::RTUClient class.
13
+
14
+ 2009-04-17 James Sanders <sanderjd@gmail.com>
15
+ * lib/rmodbus/exceptions.rb: Inherit from StandardError instead of
16
+ Exception
17
+ 2010-01-7 Timin Aleksey <atimin@gmail.com>
18
+ * lib/rmodbus/tcp_client.rb: Fixed bug of overflow transaction counter (thanks
19
+ Tallak Tveide)
20
+
data/README CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  == Feature
6
6
 
7
- * Client ModBus-TCP
8
- * Server ModBus-TCP
7
+ * Client\Server ModBus-TCP
8
+ * Client ModBus-RTU (experimental) with serialport gem
9
9
  * Support functions:
10
10
  * 01 (0x01) Read Coils
11
11
  * 02 (0x02) Read Discrete Inputs
@@ -15,6 +15,7 @@
15
15
  * 06 (0x06) Write Single Register
16
16
  * 15 (0x0F) Write Multiple Coils
17
17
  * 16 (0x10) Write Multiple registers
18
+ * 22 (0x16) Mask Write register
18
19
 
19
20
  == Installation
20
21
 
@@ -30,21 +31,18 @@ Download and install RModBus with the following
30
31
 
31
32
  $ gem install --remote rmodbus
32
33
 
33
- == CSM
34
+ == GitHub
34
35
 
35
- You can checkout source code from SVN repositry
36
+ You can checkout source code from GitHub repositry
36
37
 
37
-
38
- $ svn checkout svn://rubyforge.org/var/svn/rmodbu/trunk
39
-
40
- or
41
-
42
- $ svn checkout http://rubyforge.org/var/svn/rmodbus/trunk
38
+ $ git clone git://github.com/flipback/RModBus.git
43
39
 
44
40
  == Reference
45
41
 
46
42
  RModBus project: http://rubyforge.org/projects/rmodbus
47
43
 
44
+ RModBud on GitHub: http://github.com/flipback/RModBus
45
+
48
46
  ModBus community: http://www.modbus-ida.org
49
47
 
50
48
 
@@ -13,113 +13,8 @@
13
13
  # GNU General Public License for more details.
14
14
 
15
15
  require 'rmodbus/exceptions'
16
+ require 'rmodbus/ext'
16
17
 
17
- class String
18
-
19
-
20
- if RUBY_VERSION.to_f == 1.9
21
- def to_array_int16
22
- array_int16 = []
23
- i = 0
24
- while(i < self.bytesize) do
25
- array_int16 << self.getbyte(i) * 256 + self.getbyte(i+1)
26
- i += 2
27
- end
28
- array_int16
29
- end
30
-
31
- def to_array_bytes
32
- array_bytes = []
33
- self.each_byte do |b|
34
- array_bytes << b
35
- end
36
- array_bytes
37
- end
38
-
39
- def to_int16
40
- self.getbyte(0)*256 + self.getbyte(1)
41
- end
42
-
43
- else
44
- def to_array_int16
45
- array_int16 = []
46
- i = 0
47
- while(i < self.size) do
48
- array_int16 << (self[i].to_i)*256 + self[i+1].to_i
49
- i += 2
50
- end
51
- array_int16
52
- end
53
-
54
- def to_array_bytes
55
- array_bytes = []
56
- i = 0
57
- while(i<self.size) do
58
- array_bytes << self[i].to_i
59
- i += 1
60
- end
61
- array_bytes
62
- end
63
-
64
- def to_int16
65
- self[0]*256 + self[1]
66
- end
67
- end
68
-
69
-
70
- def to_array_bit
71
- array_bit = []
72
- self.each_byte do |byte|
73
- mask = 0x01
74
- 8.times {
75
- unless (mask & byte) == 0
76
- array_bit << 1
77
- else
78
- array_bit << 0
79
- end
80
- mask = mask << 1
81
- }
82
- end
83
- array_bit
84
- end
85
-
86
- end
87
-
88
- class Integer
89
- def to_bytes
90
- (self >> 8).chr + (self & 0xff).chr
91
- end
92
- end
93
-
94
- class Array
95
-
96
- def to_ints16
97
- s = ""
98
- self.each do |int16|
99
- s << int16.to_bytes
100
- end
101
- s
102
- end
103
-
104
- def bits_to_bytes
105
- int16 = 0
106
- s = ""
107
- mask = 0x01
108
-
109
- self.each do |bit|
110
- int16 |= mask if bit > 0
111
- mask <<= 1
112
- if mask == 0x100
113
- mask = 0x01
114
- s << int16.chr
115
- int16 = 0
116
- end
117
- end
118
- s << int16.chr unless mask == 0x01
119
- s
120
- end
121
-
122
- end
123
18
 
124
19
  module ModBus
125
20
 
@@ -127,21 +22,23 @@ module ModBus
127
22
 
128
23
  include Errors
129
24
  # Number of times to retry on connection and read timeouts
130
- CONNECTION_RETRIES = 3
131
- READ_RETRIES = 3
25
+ attr_accessor :connection_retries, :read_retries
132
26
 
27
+ def initialize
28
+ @connection_retries = 10
29
+ @read_retries = 10
30
+ end
133
31
  # Read value *ncoils* coils starting with *addr*
134
- #
135
32
  # Return array of their values
136
33
  def read_coils(addr, ncoils)
137
- query("\x1" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
34
+ query("\x1" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
138
35
  end
139
36
 
140
37
  # Read value *ncoils* discrete inputs starting with *addr*
141
38
  #
142
39
  # Return array of their values
143
40
  def read_discrete_inputs(addr, ncoils)
144
- query("\x2" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
41
+ query("\x2" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
145
42
  end
146
43
 
147
44
  # Deprecated version of read_discrete_inputs
@@ -154,14 +51,14 @@ module ModBus
154
51
  #
155
52
  # Return array of their values
156
53
  def read_holding_registers(addr, nreg)
157
- query("\x3" + addr.to_bytes + nreg.to_bytes).to_array_int16
54
+ query("\x3" + addr.to_word + nreg.to_word).unpack('n*')
158
55
  end
159
56
 
160
57
  # Read value *nreg* input registers starting with *addr*
161
58
  #
162
59
  # Return array of their values
163
60
  def read_input_registers(addr, nreg)
164
- query("\x4" + addr.to_bytes + nreg.to_bytes).to_array_int16
61
+ query("\x4" + addr.to_word + nreg.to_word).unpack('n*')
165
62
  end
166
63
 
167
64
  # Write *val* in *addr* coil
@@ -171,9 +68,9 @@ module ModBus
171
68
  # Return self
172
69
  def write_single_coil(addr, val)
173
70
  if val == 0
174
- query("\x5" + addr.to_bytes + 0.to_bytes)
71
+ query("\x5" + addr.to_word + 0.to_word)
175
72
  else
176
- query("\x5" + addr.to_bytes + 0xff00.to_bytes)
73
+ query("\x5" + addr.to_word + 0xff00.to_word)
177
74
  end
178
75
  self
179
76
  end
@@ -182,7 +79,7 @@ module ModBus
182
79
  #
183
80
  # Return self
184
81
  def write_single_register(addr, val)
185
- query("\x6" + addr.to_bytes + val.to_bytes)
82
+ query("\x6" + addr.to_word + val.to_word)
186
83
  self
187
84
  end
188
85
 
@@ -205,7 +102,7 @@ module ModBus
205
102
  sum >>= 8
206
103
  end
207
104
 
208
- query("\xf" + addr.to_bytes + val.size.to_bytes + nbyte.chr + s_val)
105
+ query("\xf" + addr.to_word + val.size.to_word + nbyte.chr + s_val)
209
106
  self
210
107
  end
211
108
 
@@ -217,10 +114,10 @@ module ModBus
217
114
  def write_multiple_registers(addr, val)
218
115
  s_val = ""
219
116
  val.each do |reg|
220
- s_val << reg.to_bytes
117
+ s_val << reg.to_word
221
118
  end
222
119
 
223
- query("\x10" + addr.to_bytes + val.size.to_bytes + (val.size * 2).chr + s_val)
120
+ query("\x10" + addr.to_word + val.size.to_word + (val.size * 2).chr + s_val)
224
121
  self
225
122
  end
226
123
 
@@ -228,7 +125,7 @@ module ModBus
228
125
  #
229
126
  # Return self
230
127
  def mask_write_register(addr, and_mask, or_mask)
231
- query("\x16" + addr.to_bytes + and_mask.to_bytes + or_mask.to_bytes)
128
+ query("\x16" + addr.to_word + and_mask.to_word + or_mask.to_word)
232
129
  self
233
130
  end
234
131
 
@@ -239,11 +136,13 @@ module ModBus
239
136
  begin
240
137
  timeout(1, ModBusTimeout) do
241
138
  pdu = read_pdu
242
- end
139
+ end
243
140
  rescue ModBusTimeout => err
244
141
  tried += 1
245
- retry unless tried >= READ_RETRIES
246
- raise ModBusTimeout.new, 'Timed out during read attempt'
142
+ retry unless tried >= @read_retries
143
+ raise ModBusTimeout.new, "Timed out during read attempt"
144
+ rescue
145
+ raise ModBusException.new, "Server did not respond"
247
146
  end
248
147
 
249
148
  if pdu[0].to_i >= 0x80
@@ -0,0 +1,61 @@
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ #
3
+ # Copyright (C) 2009 Timin Aleksey
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ class String
16
+
17
+ unless RUBY_VERSION.to_f == 1.9
18
+ def getbyte(index)
19
+ self[index].to_i
20
+ end
21
+ end
22
+
23
+ def unpack_bits
24
+ array_bit = []
25
+ self.unpack('b*')[0].each_char do |b|
26
+ array_bit << b.to_i
27
+ end
28
+ array_bit
29
+ end
30
+
31
+ end
32
+
33
+ class Integer
34
+
35
+ def to_word
36
+ (self >> 8).chr + (self & 0xff).chr
37
+ end
38
+
39
+ end
40
+
41
+ class Array
42
+
43
+ def pack_to_word
44
+ word = 0
45
+ s = ""
46
+ mask = 0x01
47
+
48
+ self.each do |bit|
49
+ word |= mask if bit > 0
50
+ mask <<= 1
51
+ if mask == 0x100
52
+ mask = 0x01
53
+ s << word.chr
54
+ word = 0
55
+ end
56
+ end
57
+ s << word.chr unless mask == 0x01
58
+ end
59
+
60
+ end
61
+
@@ -0,0 +1,86 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue
4
+ end
5
+ require 'serialport'
6
+
7
+ module ModBus
8
+
9
+ class RTUClient < Client
10
+
11
+ def initialize(port, rate=9600, slaveaddr=1)
12
+ @port = SerialPort.new(port, rate)
13
+ @slave = slaveaddr
14
+ end
15
+
16
+ protected
17
+ def send_pdu(pdu)
18
+ msg = @slave.chr + pdu
19
+ msg << crc16(msg).to_word
20
+ @port.write msg
21
+ end
22
+
23
+ def read_pdu
24
+ msg = @port.read
25
+ if msg.getbyte(0) == @slave
26
+ return msg[1..-3] if msg[-2,2] == crc16(msg[0..-3]).to_word
27
+ end
28
+ loop do
29
+ #waite timeout
30
+ end
31
+ end
32
+
33
+ def crc16(msg)
34
+ crc_lo = 0xff
35
+ crc_hi = 0xff
36
+
37
+ msg.unpack('c*').each do |byte|
38
+ i = crc_hi ^ byte
39
+ crc_hi = crc_lo ^ CrcHiTable[i]
40
+ crc_lo = CrcLoTable[i]
41
+ end
42
+
43
+ return ((crc_hi << 8) + crc_lo)
44
+ end
45
+
46
+ CrcHiTable = [
47
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
48
+ 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
49
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
50
+ 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
51
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
52
+ 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
53
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
54
+ 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
55
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
56
+ 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
57
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
58
+ 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
59
+ 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
60
+ 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
61
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
62
+ 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
63
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
64
+ 0x40]
65
+ CrcLoTable = [
66
+ 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
67
+ 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
68
+ 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
69
+ 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
70
+ 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
71
+ 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
72
+ 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
73
+ 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
74
+ 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
75
+ 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
76
+ 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
77
+ 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
78
+ 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
79
+ 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
80
+ 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
81
+ 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
82
+ 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
83
+ 0x40]
84
+ end
85
+
86
+ end
@@ -35,7 +35,7 @@ module ModBus
35
35
  end
36
36
  rescue ModBusTimeout => err
37
37
  tried += 1
38
- retry unless tried >= CONNECTION_RETRIES
38
+ retry unless tried >= @connection_retries
39
39
  raise ModBusTimeout.new, 'Timed out attempting to create connection'
40
40
  end
41
41
  @slave = slaveaddr
@@ -51,16 +51,17 @@ module ModBus
51
51
 
52
52
  private
53
53
  def send_pdu(pdu)
54
+ @@transaction = 0 if @@transaction.next > 65535
54
55
  @@transaction += 1
55
- @sock.write @@transaction.to_bytes + "\0\0" + (pdu.size + 1).to_bytes + @slave.chr + pdu
56
+ @sock.write @@transaction.to_word + "\0\0" + (pdu.size + 1).to_word + @slave.chr + pdu
56
57
  end
57
58
 
58
59
  def read_pdu
59
60
  header = @sock.read(7)
60
61
  if header
61
- tin = header[0,2].to_int16
62
+ tin = header[0,2].unpack('n')[0]
62
63
  raise Errors::ModBusException.new("Transaction number mismatch") unless tin == @@transaction
63
- len = header[4,2].to_int16
64
+ len = header[4,2].unpack('n')[0]
64
65
  @sock.read(len-1)
65
66
  else
66
67
  raise Errors::ModBusException.new("Server did not respond")
@@ -17,12 +17,10 @@ require 'gserver'
17
17
 
18
18
  module ModBus
19
19
 
20
- class TCPServer < GServer
20
+ class TCPServer < GServer
21
21
 
22
22
  attr_accessor :coils, :discret_inputs, :holding_registers, :input_registers
23
23
 
24
-
25
-
26
24
  Funcs = [1,2,3,4,5,6,15,16]
27
25
 
28
26
  def initialize(port = 502, uid = 1)
@@ -37,27 +35,16 @@ module ModBus
37
35
  def serve(io)
38
36
  loop do
39
37
  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
44
- end
45
- else
46
- if req[2,2] != "\x00\x00" or req[6].to_i != @uid
47
- io.close
48
- break
49
- end
38
+ if req[2,2] != "\x00\x00" or req.getbyte(6) != @uid
39
+ io.close
40
+ break
50
41
  end
51
42
 
52
43
  tr = req[0,2]
53
- len = req[4,2].to_int16
44
+ len = req[4,2].unpack('n')[0]
54
45
  req = io.read(len - 1)
55
46
 
56
- if RUBY_VERSION.to_f == 1.9
57
- func = req.getbyte(0)
58
- else
59
- func = req[0].to_i
60
- end
47
+ func = req.getbyte(0)
61
48
 
62
49
  unless Funcs.include?(func)
63
50
  param = { :err => 1 }
@@ -67,24 +54,24 @@ module ModBus
67
54
  when 1
68
55
  param = parse_read_func(req, @coils)
69
56
  if param[:err] == 0
70
- val = @coils[param[:addr],param[:quant]].bits_to_bytes
57
+ val = @coils[param[:addr],param[:quant]].pack_to_word
71
58
  res = func.chr + val.size.chr + val
72
59
  end
73
60
  when 2
74
61
  param = parse_read_func(req, @discret_inputs)
75
62
  if param[:err] == 0
76
- val = @discret_inputs[param[:addr],param[:quant]].bits_to_bytes
63
+ val = @discret_inputs[param[:addr],param[:quant]].pack_to_word
77
64
  res = func.chr + val.size.chr + val
78
65
  end
79
66
  when 3
80
67
  param = parse_read_func(req, @holding_registers)
81
68
  if param[:err] == 0
82
- res = func.chr + (param[:quant] * 2).chr + @holding_registers[param[:addr],param[:quant]].to_ints16
69
+ res = func.chr + (param[:quant] * 2).chr + @holding_registers[param[:addr],param[:quant]].pack('n*')
83
70
  end
84
71
  when 4
85
72
  param = parse_read_func(req, @input_registers)
86
73
  if param[:err] == 0
87
- res = func.chr + (param[:quant] * 2).chr + @input_registers[param[:addr],param[:quant]].to_ints16
74
+ res = func.chr + (param[:quant] * 2).chr + @input_registers[param[:addr],param[:quant]].pack('n*')
88
75
  end
89
76
  when 5
90
77
  param = parse_write_coil_func(req)
@@ -112,7 +99,7 @@ module ModBus
112
99
  end
113
100
  end
114
101
  if param[:err] == 0
115
- resp = tr + "\0\0" + (res.size + 1).to_bytes + @uid.chr + res
102
+ resp = tr + "\0\0" + (res.size + 1).to_word + @uid.chr + res
116
103
  else
117
104
  resp = tr + "\0\0\0\3" + @uid.chr + (func | 0x80).chr + param[:err].chr
118
105
  end
@@ -123,21 +110,21 @@ module ModBus
123
110
  private
124
111
 
125
112
  def parse_read_func(req, field)
126
- quant = req[3,2].to_int16
113
+ quant = req[3,2].unpack('n')[0]
127
114
 
128
115
  return { :err => 3} unless quant <= 0x7d
129
116
 
130
- addr = req[1,2].to_int16
117
+ addr = req[1,2].unpack('n')[0]
131
118
  return { :err => 2 } unless addr + quant <= field.size
132
119
 
133
120
  return { :err => 0, :quant => quant, :addr => addr }
134
121
  end
135
122
 
136
123
  def parse_write_coil_func(req)
137
- addr = req[1,2].to_int16
124
+ addr = req[1,2].unpack('n')[0]
138
125
  return { :err => 2 } unless addr <= @coils.size
139
126
 
140
- val = req[3,2].to_int16
127
+ val = req[3,2].unpack('n')[0]
141
128
  return { :err => 3 } unless val == 0 or val == 0xff00
142
129
 
143
130
  val = 1 if val == 0xff00
@@ -145,10 +132,10 @@ module ModBus
145
132
  end
146
133
 
147
134
  def parse_write_register_func(req)
148
- addr = req[1,2].to_int16
135
+ addr = req[1,2].unpack('n')[0]
149
136
  return { :err => 2 } unless addr <= @coils.size
150
137
 
151
- val = req[3,2].to_int16
138
+ val = req[3,2].unpack('n')[0]
152
139
 
153
140
  return { :err => 0, :addr => addr, :val => val }
154
141
  end
@@ -157,7 +144,7 @@ module ModBus
157
144
  param = parse_read_func(req, @coils)
158
145
 
159
146
  if param[:err] == 0
160
- param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant]].to_array_bit }
147
+ param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant]].unpack_bits }
161
148
  end
162
149
  param
163
150
  end
@@ -166,7 +153,7 @@ module ModBus
166
153
  param = parse_read_func(req, @holding_registers)
167
154
 
168
155
  if param[:err] == 0
169
- param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant] * 2].to_array_int16 }
156
+ param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant] * 2].unpack('n*')}
170
157
  end
171
158
  param
172
159
  end
data/lib/rmodbus.rb CHANGED
@@ -11,3 +11,5 @@
11
11
  # GNU General Public License for more details.
12
12
  require 'rmodbus/tcp_client'
13
13
  require 'rmodbus/tcp_server'
14
+ require 'rmodbus/rtu_client'
15
+
data/spec/ext_spec.rb CHANGED
@@ -2,33 +2,13 @@ require 'rmodbus'
2
2
 
3
3
  describe Array do
4
4
 
5
- it "should return string reprisent 16bit" do
6
- arr = [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
7
- arr.bits_to_bytes.should == "\xcd\x6b\x5"
8
- end
9
-
10
- it "should return string reprisent 16bit fot 8 bits" do
11
- arr = [1,0,1,1, 0,0,1,1]
12
- arr.bits_to_bytes.should == "\xcd"
5
+ before do
6
+ @arr = [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
13
7
  end
14
8
 
15
- it "should return string reprisent 16ints" do
16
- arr = [1,2]
17
- arr.to_ints16 == "\x0\x1\x0\x2"
9
+ it "should return string reprisent 16bit" do
10
+ @arr.pack_to_word == "\xcd\x6b\x5"
18
11
  end
19
12
 
20
13
  end
21
14
 
22
- describe String do
23
-
24
- it "should return array of int16" do
25
- str = "\x1\x2\x3\x4\x5\x6"
26
- str.to_array_int16.should == [0x102, 0x304, 0x506]
27
- end
28
-
29
- it "should have convert 2 chars to word" do
30
- str = "\x1\x1"
31
- str.to_int16.should == 257
32
- end
33
-
34
- end
@@ -0,0 +1,40 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue
4
+ end
5
+ require 'rmodbus'
6
+
7
+ include ModBus
8
+
9
+ describe RTUClient do
10
+
11
+ before do
12
+ @port = mock('Serial port')
13
+ SerialPort.should_receive(:new).with("/dev/port1", 9600).and_return(@port)
14
+ @mb_client = RTUClient.new("/dev/port1", 9600, 1)
15
+ @mb_client.read_retries = 0
16
+ end
17
+
18
+ it "should ignore frame with other UID" do
19
+ request = "\x10\x0\x1\x0\x1\x2\xff\xff"
20
+ @port.should_receive(:write).with("\1#{request}\xA6\x31")
21
+ @port.should_receive(:read).and_return("\x2\x10\x0\x1\x0\x1\x1C\x08")
22
+ lambda {@mb_client.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
+ @port.should_receive(:write).with("\1#{request}\xA6\x31")
28
+ @port.should_receive(:read).and_return("\x1\x10\x0\x1\x0\x1\x1C\x08")
29
+ lambda {@mb_client.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
+ @port.should_receive(:write).with("\1#{request}\xd5\xca")
35
+ @port.should_receive(:read).and_return("\x1\x3\x2\xff\xff\xb9\xf4")
36
+ @mb_client.query(request).should == "\xff\xff"
37
+ end
38
+
39
+ end
40
+
@@ -6,7 +6,7 @@ describe TCPClient, "method 'query'" do
6
6
 
7
7
  UID = 1
8
8
 
9
- before do
9
+ before(:each) do
10
10
  @sock = mock("Socket")
11
11
  @adu = "\000\001\000\000\000\001\001"
12
12
 
@@ -17,14 +17,14 @@ describe TCPClient, "method 'query'" do
17
17
  end
18
18
 
19
19
  it 'should send valid MBAP Header' do
20
- @adu[0,2] = TCPClient.transaction.next.to_bytes
20
+ @adu[0,2] = TCPClient.transaction.next.to_word
21
21
  @sock.should_receive(:write).with(@adu)
22
22
  @sock.should_receive(:read).with(7).and_return(@adu)
23
23
  @mb_client.query('').should == nil
24
24
  end
25
25
 
26
26
  it 'should throw exception if get other transaction' do
27
- @adu[0,2] = TCPClient.transaction.next.to_bytes
27
+ @adu[0,2] = TCPClient.transaction.next.to_word
28
28
  @sock.should_receive(:write).with(@adu)
29
29
  @sock.should_receive(:read).with(7).and_return("\000\002\000\000\000\001" + UID.chr)
30
30
  begin
@@ -37,7 +37,7 @@ describe TCPClient, "method 'query'" do
37
37
  it 'should return only data from PDU' do
38
38
  request = "\x3\x0\x6b\x0\x3"
39
39
  response = "\x3\x6\x2\x2b\x0\x0\x0\x64"
40
- @adu = TCPClient.transaction.next.to_bytes + "\x0\x0\x0\x9" + UID.chr + request
40
+ @adu = TCPClient.transaction.next.to_word + "\x0\x0\x0\x9" + UID.chr + request
41
41
  @sock.should_receive(:write).with(@adu[0,4] + "\0\6" + UID.chr + request)
42
42
  @sock.should_receive(:read).with(7).and_return(@adu[0,7])
43
43
  @sock.should_receive(:read).with(8).and_return(response)
@@ -10,6 +10,7 @@ describe TCPServer do
10
10
  @server.input_registers = [1,2,3,4]
11
11
  @server.start
12
12
  @client = ModBus::TCPClient.new('127.0.0.1', 8502, 1)
13
+ @client.read_retries = 0
13
14
  end
14
15
 
15
16
  it "should silent if UID has mismatched" do
@@ -20,15 +21,18 @@ describe TCPServer do
20
21
  rescue ModBus::Errors::ModBusException => ex
21
22
  ex.message.should == "Server did not respond"
22
23
  end
24
+ client.close
23
25
  end
24
26
 
25
27
  it "should silent if protocol identifer has mismatched" do
28
+ @client.close
26
29
  client = TCPSocket.new('127.0.0.1', 8502)
27
30
  begin
28
31
  client.write "\0\0\1\0\0\6\1"
29
32
  rescue ModBus::Errors::ModBusException => ex
30
33
  ex.message.should == "Server did not respond"
31
34
  end
35
+ client.close
32
36
  end
33
37
 
34
38
  it "should send exception if function not supported" do
@@ -55,6 +59,12 @@ describe TCPServer do
55
59
  end
56
60
  end
57
61
 
62
+ it "should calc a many requests" do
63
+ @client.read_coils(1,2)
64
+ @client.write_multiple_registers(0,[9,9,9,])
65
+ @client.read_holding_registers(0,3).should == [9,9,9]
66
+ end
67
+
58
68
  it "should supported function 'read coils'" do
59
69
  @client.read_coils(0,3).should == @server.coils[0,3]
60
70
  end
@@ -95,15 +105,10 @@ describe TCPServer do
95
105
  @server.holding_registers.should == [1,2,3,1,2,3,4,5,9]
96
106
  end
97
107
 
98
- it "should valid respond on query reading more than 8 coils at once bug#26387" do
99
- @server.coils = [0,0,1,1,0,0,1,1,0,0]
100
- @client.read_coils(0,7)
101
- @client.read_coils(1,9)
102
- @client.read_coils(0,8)
103
- end
104
-
105
108
  after do
106
- @client.close if @client
107
- @server.stop if @server
109
+ @client.close
110
+ @server.stop unless @server.stopped?
111
+ while GServer.in_service?(8502)
112
+ end
108
113
  end
109
114
  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.3
4
+ version: 0.3.0
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-06-22 00:00:00 +05:00
12
+ date: 2010-01-07 00:00:00 +05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -23,23 +23,26 @@ extra_rdoc_files:
23
23
  - README
24
24
  - AUTHORS
25
25
  - LICENSE
26
- - CHANGES
26
+ - ChangeLog
27
27
  files:
28
- - lib/rmodbus.rb
29
- - lib/rmodbus/exceptions.rb
30
28
  - lib/rmodbus/tcp_client.rb
31
- - lib/rmodbus/client.rb
29
+ - lib/rmodbus/rtu_client.rb
32
30
  - lib/rmodbus/tcp_server.rb
33
- - examples/add_new_function.rb
31
+ - lib/rmodbus/exceptions.rb
32
+ - lib/rmodbus/client.rb
33
+ - lib/rmodbus/ext.rb
34
+ - lib/rmodbus.rb
34
35
  - examples/use_tcp_modbus.rb
36
+ - examples/add_new_function.rb
35
37
  - spec/ext_spec.rb
36
- - spec/client_spec.rb
37
38
  - spec/tcp_client_spec.rb
39
+ - spec/rtu_client_spec.rb
40
+ - spec/client_spec.rb
38
41
  - spec/tcp_server_spec.rb
39
42
  - README
40
43
  - AUTHORS
41
44
  - LICENSE
42
- - CHANGES
45
+ - ChangeLog
43
46
  has_rdoc: true
44
47
  homepage:
45
48
  licenses: []
@@ -68,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
71
  requirements: []
69
72
 
70
73
  rubyforge_project:
71
- rubygems_version: 1.3.4
74
+ rubygems_version: 1.3.5
72
75
  signing_key:
73
76
  specification_version: 3
74
77
  summary: RModBus - free implementation of protocol ModBus
data/CHANGES DELETED
@@ -1,17 +0,0 @@
1
- 2008-25-01 Release-0.1.1
2
- - fixed bug in function "read discrete inputs (0x2)" of ModBus client
3
-
4
- 2009-30-01 Release-0.2
5
- - add ModBus-TCP server
6
- - add retries for tcp connection and read, default of 10 retries, still 1 second timeout
7
- - fix typos
8
-
9
- 2009-09-02 Release-0.2.1
10
- - add support ruby 1.9.1
11
- - fix bug in TCPServer class (it don't close connection after first request now)
12
-
13
- 2009-17-04 Release-0.2.2
14
- - Make exceptions inherit from StandardError instead of Exception
15
-
16
- 2009-22-06 Release-0.2.3
17
- - fix bug #26387