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