@@ -20,24 +20,31 @@ def initialize(info = {})
20
20
'Author' =>
21
21
[
22
22
'EsMnemon <esm[at]mnemonic.no>' , # original write-only module
23
- 'Arnaud SOULLIE <arnaud.soullie[at]solucom.fr>' # new code that allows read/write
23
+ 'Arnaud SOULLIE <arnaud.soullie[at]solucom.fr>' , # code that allows read/write
24
+ 'Alexandrine TORRENTS <alexandrine.torrents[at]eurecom.fr>' , # code that allows reading/writing at multiple consecutive addresses
25
+ 'Mathieu CHEVALIER <mathieu.chevalier[at]eurecom.fr>'
24
26
] ,
25
27
'License' => MSF_LICENSE ,
26
28
'Actions' =>
27
29
[
28
- [ 'READ_COIL' , { 'Description' => 'Read one bit from a coil' } ] ,
30
+ [ 'READ_COILS' , { 'Description' => 'Read bits from several coils' } ] ,
31
+ [ 'READ_REGISTERS' , { 'Description' => 'Read words from several registers' } ] ,
29
32
[ 'WRITE_COIL' , { 'Description' => 'Write one bit to a coil' } ] ,
30
- [ 'READ_REGISTER' , { 'Description' => 'Read one word from a register' } ] ,
31
- [ 'WRITE_REGISTER' , { 'Description' => 'Write one word to a register' } ]
33
+ [ 'WRITE_REGISTER' , { 'Description' => 'Write one word to a register' } ] ,
34
+ [ 'WRITE_COILS' , { 'Description' => 'Write bits to several coils' } ] ,
35
+ [ 'WRITE_REGISTERS' , { 'Description' => 'Write words to several registers' } ]
32
36
] ,
33
- 'DefaultAction' => 'READ_REGISTER '
37
+ 'DefaultAction' => 'READ_REGISTERS '
34
38
) )
35
39
36
40
register_options (
37
41
[
38
42
Opt ::RPORT ( 502 ) ,
39
- OptInt . new ( 'DATA' , [ false , "Data to write (WRITE_COIL and WRITE_REGISTER modes only)" ] ) ,
40
43
OptInt . new ( 'DATA_ADDRESS' , [ true , "Modbus data address" ] ) ,
44
+ OptInt . new ( 'NUMBER' , [ false , "Number of coils/registers to read (READ_COILS ans READ_REGISTERS modes only)" , 1 ] ) ,
45
+ OptInt . new ( 'DATA' , [ false , "Data to write (WRITE_COIL and WRITE_REGISTER modes only)" ] ) ,
46
+ OptString . new ( 'DATA_COILS' , [ false , "Data in binary to write (WRITE_COILS mode only) e.g. 0110" ] ) ,
47
+ OptString . new ( 'DATA_REGISTERS' , [ false , "Words to write to each register separated with a comma (WRITE_REGISTERS mode only) e.g. 1,2,3,4" ] ) ,
41
48
OptInt . new ( 'UNIT_NUMBER' , [ false , "Modbus unit number" , 1 ] ) ,
42
49
] , self . class )
43
50
@@ -63,7 +70,7 @@ def make_read_payload
63
70
payload = [ datastore [ 'UNIT_NUMBER' ] ] . pack ( "c" )
64
71
payload += [ @function_code ] . pack ( "c" )
65
72
payload += [ datastore [ 'DATA_ADDRESS' ] ] . pack ( "n" )
66
- payload += [ 1 ] . pack ( "n" )
73
+ payload += [ datastore [ 'NUMBER' ] ] . pack ( "n" )
67
74
make_payload ( payload )
68
75
end
69
76
@@ -79,6 +86,21 @@ def make_write_coil_payload(data)
79
86
packet_data
80
87
end
81
88
89
+ def make_write_coils_payload ( data , byte )
90
+ payload = [ datastore [ 'UNIT_NUMBER' ] ] . pack ( "c" )
91
+ payload += [ @function_code ] . pack ( "c" )
92
+ payload += [ datastore [ 'DATA_ADDRESS' ] ] . pack ( "n" )
93
+ payload += [ datastore [ 'DATA_COILS' ] . size ] . pack ( "n" ) # bit count
94
+ payload += [ byte ] . pack ( "c" ) # byte count
95
+ for i in 0 ..( byte -1 )
96
+ payload += [ data [ i ] ] . pack ( "b*" )
97
+ end
98
+
99
+ packet_data = make_payload ( payload )
100
+
101
+ packet_data
102
+ end
103
+
82
104
def make_write_register_payload ( data )
83
105
payload = [ datastore [ 'UNIT_NUMBER' ] ] . pack ( "c" )
84
106
payload += [ @function_code ] . pack ( "c" )
@@ -88,6 +110,19 @@ def make_write_register_payload(data)
88
110
make_payload ( payload )
89
111
end
90
112
113
+ def make_write_registers_payload ( data , size )
114
+ payload = [ datastore [ 'UNIT_NUMBER' ] ] . pack ( "c" )
115
+ payload += [ @function_code ] . pack ( "c" )
116
+ payload += [ datastore [ 'DATA_ADDRESS' ] ] . pack ( "n" )
117
+ payload += [ size ] . pack ( "n" ) # word count
118
+ payload += [ 2 *size ] . pack ( "c" ) # byte count
119
+ for i in 0 ..( size -1 )
120
+ payload += [ data [ i ] ] . pack ( "n" )
121
+ end
122
+
123
+ make_payload ( payload )
124
+ end
125
+
91
126
def handle_error ( response )
92
127
case response . reverse . unpack ( "c" ) [ 0 ] . to_i
93
128
when 1
@@ -106,34 +141,57 @@ def handle_error(response)
106
141
return
107
142
end
108
143
109
- def read_coil
144
+ def read_coils
145
+ if datastore [ 'NUMBER' ] +datastore [ 'DATA_ADDRESS' ] > 65535
146
+ print_error ( "Coils addresses go from 0 to 65535. You cannot go beyond." )
147
+ return
148
+ end
110
149
@function_code = 0x1
111
- print_status ( "Sending READ COIL ..." )
150
+ print_status ( "Sending READ COILS ..." )
112
151
response = send_frame ( make_read_payload )
152
+ values = [ ]
113
153
if response . nil?
114
- print_error ( "No answer for the READ COIL " )
154
+ print_error ( "No answer for the READ COILS " )
115
155
return
116
156
elsif response . unpack ( "C*" ) [ 7 ] == ( 0x80 | @function_code )
117
157
handle_error ( response )
118
158
elsif response . unpack ( "C*" ) [ 7 ] == @function_code
119
- value = response [ 9 ] . unpack ( "c" ) [ 0 ]
120
- print_good ( "Coil value at address #{ datastore [ 'DATA_ADDRESS' ] } : #{ value } " )
159
+ loop = ( datastore [ 'NUMBER' ] -1 ) /8
160
+ for i in 0 ..loop
161
+ bin_value = response [ 9 +i ] . unpack ( "b*" ) [ 0 ]
162
+ list = bin_value . split ( "" )
163
+ for j in 0 ..7
164
+ list [ j ] = list [ j ] . to_i
165
+ values [ i *8 + j ] = list [ j ]
166
+ end
167
+ end
168
+ values = values [ 0 ..( datastore [ 'NUMBER' ] -1 ) ]
169
+ print_good ( "#{ datastore [ 'NUMBER' ] } coil values from address #{ datastore [ 'DATA_ADDRESS' ] } : " )
170
+ print_good ( "#{ values } " )
121
171
else
122
172
print_error ( "Unknown answer" )
123
173
end
124
174
end
125
175
126
- def read_register
176
+ def read_registers
177
+ if datastore [ 'NUMBER' ] +datastore [ 'DATA_ADDRESS' ] > 65535
178
+ print_error ( "Registers addresses go from 0 to 65535. You cannot go beyond." )
179
+ return
180
+ end
127
181
@function_code = 3
128
- print_status ( "Sending READ REGISTER ..." )
182
+ print_status ( "Sending READ REGISTERS ..." )
129
183
response = send_frame ( make_read_payload )
184
+ values = [ ]
130
185
if response . nil?
131
- print_error ( "No answer for the READ REGISTER " )
186
+ print_error ( "No answer for the READ REGISTERS " )
132
187
elsif response . unpack ( "C*" ) [ 7 ] == ( 0x80 | @function_code )
133
188
handle_error ( response )
134
189
elsif response . unpack ( "C*" ) [ 7 ] == @function_code
135
- value = response [ 9 ..10 ] . unpack ( "n" ) [ 0 ]
136
- print_good ( "Register value at address #{ datastore [ 'DATA_ADDRESS' ] } : #{ value } " )
190
+ for i in 0 ..( datastore [ 'NUMBER' ] -1 )
191
+ values . push ( response [ 9 +2 *i ..10 +2 *i ] . unpack ( "n" ) [ 0 ] )
192
+ end
193
+ print_good ( "#{ datastore [ 'NUMBER' ] } register values from address #{ datastore [ 'DATA_ADDRESS' ] } : " )
194
+ print_good ( "#{ values } " )
137
195
else
138
196
print_error ( "Unknown answer" )
139
197
end
@@ -162,6 +220,39 @@ def write_coil
162
220
end
163
221
end
164
222
223
+ def write_coils
224
+ @function_code = 15
225
+ temp = datastore [ 'DATA_COILS' ]
226
+ check = temp . split ( "" )
227
+ if temp . size > 65535
228
+ print_error ( "DATA_COILS size must be between 0 and 65535" )
229
+ return
230
+ end
231
+ for j in check
232
+ if j =="0" or j =="1"
233
+ else
234
+ print_error ( "DATA_COILS value must only contain 0s and 1s without space" )
235
+ return
236
+ end
237
+ end
238
+ byte_number = ( temp . size -1 ) /8 + 1
239
+ data = [ ]
240
+ for i in 0 ..( byte_number -1 )
241
+ data . push ( temp [ ( i *8 +0 ) ..( i *8 +7 ) ] )
242
+ end
243
+ print_status ( "Sending WRITE COILS..." )
244
+ response = send_frame ( make_write_coils_payload ( data , byte_number ) )
245
+ if response . nil?
246
+ print_error ( "No answer for the WRITE COILS" )
247
+ elsif response . unpack ( "C*" ) [ 7 ] == ( 0x80 | @function_code )
248
+ handle_error ( response )
249
+ elsif response . unpack ( "C*" ) [ 7 ] == @function_code
250
+ print_good ( "Values #{ datastore [ 'DATA_COILS' ] } successfully written from coil address #{ datastore [ 'DATA_ADDRESS' ] } " )
251
+ else
252
+ print_error ( "Unknown answer" )
253
+ end
254
+ end
255
+
165
256
def write_register
166
257
@function_code = 6
167
258
if datastore [ 'DATA' ] < 0 || datastore [ 'DATA' ] > 65535
@@ -181,18 +272,74 @@ def write_register
181
272
end
182
273
end
183
274
275
+ def write_registers
276
+ @function_code = 16
277
+ check = datastore [ 'DATA_REGISTERS' ] . split ( "" )
278
+ for j in 0 ..( check . size -1 )
279
+ if check [ j ] == "0" or check [ j ] == "1" or check [ j ] == "2" or check [ j ] == "3" or check [ j ] == "4" or check [ j ] == "5" or check [ j ] == "6" or check [ j ] == "7" or check [ j ] == "8" or check [ j ] == "9" or check [ j ] == ","
280
+ if check [ j ] == "," and check [ j +1 ] == ","
281
+ print_error ( "DATA_REGISTERS cannot contain two consecutive commas" )
282
+ return
283
+ end
284
+ else
285
+ print_error ( "DATA_REGISTERS value must only contain numbers and commas without space" )
286
+ return
287
+ end
288
+ end
289
+ list = datastore [ 'DATA_REGISTERS' ] . split ( "," )
290
+ if list . size +datastore [ 'DATA_ADDRESS' ] > 65535
291
+ print_error ( "Registers addresses go from 0 to 65535. You cannot go beyond." )
292
+ return
293
+ end
294
+ data = [ ]
295
+ for i in 0 ..( list . size -1 )
296
+ data [ i ] = list [ i ] . to_i
297
+ end
298
+ for j in 0 ..( data . size -1 )
299
+ if data [ j ] < 0 || data [ j ] > 65535
300
+ print_error ( "Each word to write must be an integer between 0 and 65535 in WRITE_REGISTERS mode" )
301
+ return
302
+ end
303
+ end
304
+ print_status ( "Sending WRITE REGISTERS..." )
305
+ response = send_frame ( make_write_registers_payload ( data , data . size ) )
306
+ if response . nil?
307
+ print_error ( "No answer for the WRITE REGISTERS" )
308
+ elsif response . unpack ( "C*" ) [ 7 ] == ( 0x80 | @function_code )
309
+ handle_error ( response )
310
+ elsif response . unpack ( "C*" ) [ 7 ] == @function_code
311
+ print_good ( "Values #{ datastore [ 'DATA_REGISTERS' ] } successfully written from registry address #{ datastore [ 'DATA_ADDRESS' ] } " )
312
+ else
313
+ print_error ( "Unknown answer" )
314
+ end
315
+ end
316
+
184
317
def run
185
318
@modbus_counter = 0x0000 # used for modbus frames
186
319
connect
187
320
case action . name
188
- when "READ_COIL "
189
- read_coil
190
- when "READ_REGISTER "
191
- read_register
321
+ when "READ_COILS "
322
+ read_coils
323
+ when "READ_REGISTERS "
324
+ read_registers
192
325
when "WRITE_COIL"
193
326
write_coil
194
327
when "WRITE_REGISTER"
195
328
write_register
329
+ when "WRITE_COILS"
330
+ if datastore [ 'DATA_COILS' ] == nil
331
+ print_error ( "The following option is needed in WRITE_COILS mode: DATA_COILS." )
332
+ return
333
+ else
334
+ write_coils
335
+ end
336
+ when "WRITE_REGISTERS"
337
+ if datastore [ 'DATA_REGISTERS' ] == nil
338
+ print_error ( "The following option is needed in WRITE_REGISTERS mode: DATA_REGISTERS." )
339
+ return
340
+ else
341
+ write_registers
342
+ end
196
343
else
197
344
print_error ( "Invalid ACTION" )
198
345
end
0 commit comments