rmodbus 0.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -6,3 +6,8 @@
6
6
  - add retries for tcp connection and read, default of 10 retries, still 1 second timeout
7
7
  - fix typos
8
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
+
data/README CHANGED
@@ -5,6 +5,7 @@
5
5
  == Feature
6
6
 
7
7
  * Client ModBus-TCP
8
+ * Server ModBus-TCP
8
9
  * Support functions:
9
10
  * 01 (0x01) Read Coils
10
11
  * 02 (0x02) Read Discrete Inputs
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue
4
+ end
5
+ require 'rmodbus'
6
+
7
+ include ModBus
8
+
9
+ class MyTCPClient < TCPClient
10
+
11
+ def user_define_function(arg1, arg2)
12
+ query("\x65" + arg1.to_bytes + arg2.to_bytes)
13
+ end
14
+
15
+ end
16
+
17
+ @my_mb = MyTCPClient.new('127.0.0.1', 502, 1)
18
+ @my_mb.user_define_function(20,13)
19
+
@@ -0,0 +1,17 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib/')
2
+ require 'rmodbus'
3
+
4
+ srv = ModBus::TCPServer.new(8502,1)
5
+ srv.coils = [1,0,1,1]
6
+ srv.discret_inputs = [1,1,0,0]
7
+ srv.holding_registers = [1,2,3,4]
8
+ srv.input_registers = [1,2,3,4]
9
+ srv.debug = true
10
+ srv.audit = true
11
+ srv.start
12
+
13
+ cl = ModBus::TCPClient.new('127.0.0.1', 8502, 1)
14
+ puts cl.read_holding_registers(0,4)
15
+ cl.write_multiple_registers(0, [4,4,4])
16
+ puts cl.read_holding_registers(0,4)
17
+ srv.stop
@@ -1,246 +1,283 @@
1
- # RModBus - free implementation of ModBus protocol on Ruby.
2
- #
3
- # Copyright (C) 2008 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
- require 'rmodbus/exceptions'
16
-
17
- class String
18
-
19
- def to_int16
20
- self[0]*256 + self[1]
21
- end
22
-
23
- def to_array_int16
24
- array_int16 = []
25
- i = 0
26
- while(i < self.size) do
27
- array_int16 << self[i]*256 + self[i+1]
28
- i += 2
29
- end
30
- array_int16
31
- end
32
-
33
- def to_array_bit
34
- array_bit = []
35
- self.each_byte do |byte|
36
- mask = 0x01
37
- 8.times {
38
- unless (mask & byte) == 0
39
- array_bit << 1
40
- else
41
- array_bit << 0
42
- end
43
- mask = mask << 1
44
- }
45
- end
46
- array_bit
47
- end
48
-
49
- end
50
-
51
- class Integer
52
- def to_bytes
53
- (self >> 8).chr + (self & 0xff).chr
54
- end
55
- end
56
-
57
- class Array
58
-
59
- def to_ints16
60
- s = ""
61
- self.each do |int16|
62
- s << int16.to_bytes
63
- end
64
- s
65
- end
66
-
67
- def bits_to_bytes
68
- int16 = 0
69
- s = ""
70
- mask = 0x01
71
-
72
- self.each do |bit|
73
- int16 |= mask if bit > 0
74
- mask <<= 1
75
- if mask == 0x100
76
- mask = 0x01
77
- s << int16.chr
78
- int16 = 0
79
- end
80
- end
81
- s << int16.chr unless mask == 0x01
82
- end
83
-
84
- end
85
-
86
- module ModBus
87
-
88
- class Client
89
-
90
- include Errors
91
- # Number of times to retry on connection and read timeouts
92
- CONNECTION_RETRIES = 10
93
- READ_RETRIES = 10
94
-
95
- # Read value *ncoils* coils starting with *addr*
96
- #
97
- # Return array of their values
98
- def read_coils(addr, ncoils)
99
- query("\x1" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
100
- end
101
-
102
- # Read value *ncoils* discrete inputs starting with *addr*
103
- #
104
- # Return array of their values
105
- def read_discrete_inputs(addr, ncoils)
106
- query("\x2" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
107
- end
108
-
109
- # Deprecated version of read_discrete_inputs
110
- def read_discret_inputs(addr, ncoils)
111
- warn "[DEPRECATION] `read_discret_inputs` is deprecated. Please use `read_discrete_inputs` instead."
112
- read_discrete_inputs(addr, ncoils)
113
- end
114
-
115
- # Read value *nreg* holding registers starting with *addr*
116
- #
117
- # Return array of their values
118
- def read_holding_registers(addr, nreg)
119
- query("\x3" + addr.to_bytes + nreg.to_bytes).to_array_int16
120
- end
121
-
122
- # Read value *nreg* input registers starting with *addr*
123
- #
124
- # Return array of their values
125
- def read_input_registers(addr, nreg)
126
- query("\x4" + addr.to_bytes + nreg.to_bytes).to_array_int16
127
- end
128
-
129
- # Write *val* in *addr* coil
130
- #
131
- # if *val* lager 0 write 1
132
- #
133
- # Return self
134
- def write_single_coil(addr, val)
135
- if val == 0
136
- query("\x5" + addr.to_bytes + 0.to_bytes)
137
- else
138
- query("\x5" + addr.to_bytes + 0xff00.to_bytes)
139
- end
140
- self
141
- end
142
-
143
- # Write *val* in *addr* register
144
- #
145
- # Return self
146
- def write_single_register(addr, val)
147
- query("\x6" + addr.to_bytes + val.to_bytes)
148
- self
149
- end
150
-
151
- # Write *val* in coils starting with *addr*
152
- #
153
- # *val* it is array of bits
154
- #
155
- # Return self
156
- def write_multiple_coils(addr, val)
157
- nbyte = ((val.size-1) >> 3) + 1
158
- sum = 0
159
- (val.size - 1).downto(0) do |i|
160
- sum = sum << 1
161
- sum |= 1 if val[i] > 0
162
- end
163
-
164
- s_val = ""
165
- nbyte.times do
166
- s_val << (sum & 0xff).chr
167
- sum >>= 8
168
- end
169
-
170
- query("\xf" + addr.to_bytes + val.size.to_bytes + nbyte.chr + s_val)
171
- self
172
- end
173
-
174
- # Write *val* in registers starting with *addr*
175
- #
176
- # *val* it is array of integer
177
- #
178
- # Return self
179
- def write_multiple_registers(addr, val)
180
- s_val = ""
181
- val.each do |reg|
182
- s_val << reg.to_bytes
183
- end
184
-
185
- query("\x10" + addr.to_bytes + val.size.to_bytes + (val.size * 2).chr + s_val)
186
- self
187
- end
188
-
189
- # Write *current value & and_mask | or mask in *addr* register
190
- #
191
- # Return self
192
- def mask_write_register(addr, and_mask, or_mask)
193
- query("\x16" + addr.to_bytes + and_mask.to_bytes + or_mask.to_bytes)
194
- self
195
- end
196
-
197
- def query(pdu)
198
- send_pdu(pdu)
199
-
200
- tried = 0
201
- begin
202
- timeout(1, ModBusTimeout) do
203
- pdu = read_pdu
204
- end
205
- rescue ModBusTimeout => err
206
- tried += 1
207
- retry unless tried >= READ_RETRIES
208
- raise ModBusTimeout.new, 'Timed out during read attempt'
209
- end
210
-
211
- if pdu[0].to_i >= 0x80
212
- case pdu[1].to_i
213
- when 1
214
- raise IllegalFunction.new, "The function code received in the query is not an allowable action for the server"
215
- when 2
216
- raise IllegalDataAddress.new, "The data address received in the query is not an allowable address for the server"
217
- when 3
218
- raise IllegalDataValue.new, "A value contained in the query data field is not an allowable value for server"
219
- when 4
220
- raise SlaveDeviceFailure.new, "An unrecoverable error occurred while the server was attempting to perform the requested action"
221
- when 5
222
- raise Acknowledge.new, "The server has accepted the request and is processing it, but a long duration of time will be required to do so"
223
- when 6
224
- raise SlaveDeviceBus.new, "The server is engaged in processing a long duration program command"
225
- when 8
226
- raise MemoryParityError.new, "The extended file area failed to pass a consistency check"
227
- else
228
- raise ModBusException.new, "Unknown error"
229
- end
230
- end
231
- pdu[2..-1]
232
- end
233
-
234
- protected
235
- def send_pdu(pdu)
236
- end
237
-
238
- def read_pdu
239
- end
240
-
241
- def close
242
- end
243
-
244
- end
245
-
246
- end
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ #
3
+ # Copyright (C) 2008 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
+ require 'rmodbus/exceptions'
16
+
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
+ end
120
+
121
+ end
122
+
123
+ module ModBus
124
+
125
+ class Client
126
+
127
+ include Errors
128
+ # Number of times to retry on connection and read timeouts
129
+ CONNECTION_RETRIES = 3
130
+ READ_RETRIES = 3
131
+
132
+ # Read value *ncoils* coils starting with *addr*
133
+ #
134
+ # Return array of their values
135
+ def read_coils(addr, ncoils)
136
+ query("\x1" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
137
+ end
138
+
139
+ # Read value *ncoils* discrete inputs starting with *addr*
140
+ #
141
+ # Return array of their values
142
+ def read_discrete_inputs(addr, ncoils)
143
+ query("\x2" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
144
+ end
145
+
146
+ # Deprecated version of read_discrete_inputs
147
+ def read_discret_inputs(addr, ncoils)
148
+ warn "[DEPRECATION] `read_discret_inputs` is deprecated. Please use `read_discrete_inputs` instead."
149
+ read_discrete_inputs(addr, ncoils)
150
+ end
151
+
152
+ # Read value *nreg* holding registers starting with *addr*
153
+ #
154
+ # Return array of their values
155
+ def read_holding_registers(addr, nreg)
156
+ query("\x3" + addr.to_bytes + nreg.to_bytes).to_array_int16
157
+ end
158
+
159
+ # Read value *nreg* input registers starting with *addr*
160
+ #
161
+ # Return array of their values
162
+ def read_input_registers(addr, nreg)
163
+ query("\x4" + addr.to_bytes + nreg.to_bytes).to_array_int16
164
+ end
165
+
166
+ # Write *val* in *addr* coil
167
+ #
168
+ # if *val* lager 0 write 1
169
+ #
170
+ # Return self
171
+ def write_single_coil(addr, val)
172
+ if val == 0
173
+ query("\x5" + addr.to_bytes + 0.to_bytes)
174
+ else
175
+ query("\x5" + addr.to_bytes + 0xff00.to_bytes)
176
+ end
177
+ self
178
+ end
179
+
180
+ # Write *val* in *addr* register
181
+ #
182
+ # Return self
183
+ def write_single_register(addr, val)
184
+ query("\x6" + addr.to_bytes + val.to_bytes)
185
+ self
186
+ end
187
+
188
+ # Write *val* in coils starting with *addr*
189
+ #
190
+ # *val* it is array of bits
191
+ #
192
+ # Return self
193
+ def write_multiple_coils(addr, val)
194
+ nbyte = ((val.size-1) >> 3) + 1
195
+ sum = 0
196
+ (val.size - 1).downto(0) do |i|
197
+ sum = sum << 1
198
+ sum |= 1 if val[i] > 0
199
+ end
200
+
201
+ s_val = ""
202
+ nbyte.times do
203
+ s_val << (sum & 0xff).chr
204
+ sum >>= 8
205
+ end
206
+
207
+ query("\xf" + addr.to_bytes + val.size.to_bytes + nbyte.chr + s_val)
208
+ self
209
+ end
210
+
211
+ # Write *val* in registers starting with *addr*
212
+ #
213
+ # *val* it is array of integer
214
+ #
215
+ # Return self
216
+ def write_multiple_registers(addr, val)
217
+ s_val = ""
218
+ val.each do |reg|
219
+ s_val << reg.to_bytes
220
+ end
221
+
222
+ query("\x10" + addr.to_bytes + val.size.to_bytes + (val.size * 2).chr + s_val)
223
+ self
224
+ end
225
+
226
+ # Write *current value & and_mask | or mask in *addr* register
227
+ #
228
+ # Return self
229
+ def mask_write_register(addr, and_mask, or_mask)
230
+ query("\x16" + addr.to_bytes + and_mask.to_bytes + or_mask.to_bytes)
231
+ self
232
+ end
233
+
234
+ def query(pdu)
235
+ send_pdu(pdu)
236
+
237
+ tried = 0
238
+ begin
239
+ timeout(1, ModBusTimeout) do
240
+ pdu = read_pdu
241
+ end
242
+ rescue ModBusTimeout => err
243
+ tried += 1
244
+ retry unless tried >= READ_RETRIES
245
+ raise ModBusTimeout.new, 'Timed out during read attempt'
246
+ end
247
+
248
+ if pdu[0].to_i >= 0x80
249
+ case pdu[1].to_i
250
+ when 1
251
+ raise IllegalFunction.new, "The function code received in the query is not an allowable action for the server"
252
+ when 2
253
+ raise IllegalDataAddress.new, "The data address received in the query is not an allowable address for the server"
254
+ when 3
255
+ raise IllegalDataValue.new, "A value contained in the query data field is not an allowable value for server"
256
+ when 4
257
+ raise SlaveDeviceFailure.new, "An unrecoverable error occurred while the server was attempting to perform the requested action"
258
+ when 5
259
+ raise Acknowledge.new, "The server has accepted the request and is processing it, but a long duration of time will be required to do so"
260
+ when 6
261
+ raise SlaveDeviceBus.new, "The server is engaged in processing a long duration program command"
262
+ when 8
263
+ raise MemoryParityError.new, "The extended file area failed to pass a consistency check"
264
+ else
265
+ raise ModBusException.new, "Unknown error"
266
+ end
267
+ end
268
+ pdu[2..-1]
269
+ end
270
+
271
+ protected
272
+ def send_pdu(pdu)
273
+ end
274
+
275
+ def read_pdu
276
+ end
277
+
278
+ def close
279
+ end
280
+
281
+ end
282
+
283
+ end