rmodbus 0.2.3 → 0.3.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.
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