LMG_modbus 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +3 -0
- data/README +33 -0
- data/Rakefile +45 -0
- data/lib/LMG_modbus.rb +329 -0
- data/lib/example.rb +48 -0
- metadata +72 -0
data/LICENSE
ADDED
data/README
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
== LMG_modbus
|
2
|
+
|
3
|
+
## Example - Write register test 485
|
4
|
+
#
|
5
|
+
#test = Modbus485.new('com1')
|
6
|
+
#test.create_test('C:\Documents and Settings\shanksstemp\My Documents\Testing\V4Cooling2\PA\DS\LiebertDS_FDM_35_write_driver.xls')
|
7
|
+
#puts "Running Positive Tests"
|
8
|
+
#test.run_modbus_write(WRITE_POSITIVE,RESULT_POSITIVE)
|
9
|
+
#puts "Running Negative Tests"
|
10
|
+
#test.run_modbus_write(WRITE_NEGATIVE,RESULT_NEGATIVE)
|
11
|
+
#puts "Restoring Default"
|
12
|
+
#test.run_modbus_write(WRITE_DEFAULT,RESULT_DEFAULT) #default
|
13
|
+
#test.close
|
14
|
+
|
15
|
+
# Example - Write register test TCP
|
16
|
+
#
|
17
|
+
#test = ModbusTCP.new('126.4.202.199')
|
18
|
+
#test.create_test('C:\Documents and Settings\shanksstemp\Desktop\PA\DS\LiebertDS_FDM_35_writedriver.xls')
|
19
|
+
#test.run_modbus_write('g','k')
|
20
|
+
#test.close
|
21
|
+
|
22
|
+
# Example - Static Test using modpoll
|
23
|
+
|
24
|
+
#test = ModbusPoller485.new(1, 'com1','C:\Documents and Settings\shanksstemp\Desktop\XP\XDC\XDC_FDM_207.xls','c:\win32')
|
25
|
+
#test.run
|
26
|
+
|
27
|
+
#Example - Read time and set time
|
28
|
+
#test = Modbus485.new('com1')
|
29
|
+
#test.read_time_register(9998)
|
30
|
+
|
31
|
+
#test.write_time_register(9998,Time.now)
|
32
|
+
#sleep(60)
|
33
|
+
#test.read_time_register(9998)
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# To change this template, choose Tools | Templates
|
3
|
+
# and open the template in the editor.
|
4
|
+
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rake'
|
8
|
+
require 'rake/clean'
|
9
|
+
require 'rake/gempackagetask'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
require 'rake/testtask'
|
12
|
+
|
13
|
+
spec = Gem::Specification.new do |s|
|
14
|
+
s.name = 'LMG_modbus'
|
15
|
+
s.version = '1.0.2'
|
16
|
+
s.has_rdoc = true
|
17
|
+
s.extra_rdoc_files = ['README', 'LICENSE']
|
18
|
+
s.summary = 'Useful set of test automation classes for bulk polling and bulk writing modbus registers'
|
19
|
+
s.description = s.summary
|
20
|
+
s.author = ''
|
21
|
+
s.email = ''
|
22
|
+
# s.executables = ['your_executable_here']
|
23
|
+
s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
24
|
+
s.require_path = "lib"
|
25
|
+
s.bindir = "bin"
|
26
|
+
end
|
27
|
+
|
28
|
+
Rake::GemPackageTask.new(spec) do |p|
|
29
|
+
p.gem_spec = spec
|
30
|
+
p.need_tar = true
|
31
|
+
p.need_zip = true
|
32
|
+
end
|
33
|
+
|
34
|
+
Rake::RDocTask.new do |rdoc|
|
35
|
+
files =['README', 'LICENSE', 'lib/**/*.rb']
|
36
|
+
rdoc.rdoc_files.add(files)
|
37
|
+
rdoc.main = "README" # page to start on
|
38
|
+
rdoc.title = "LMG_modbus Docs"
|
39
|
+
rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
|
40
|
+
rdoc.options << '--line-numbers'
|
41
|
+
end
|
42
|
+
|
43
|
+
Rake::TestTask.new do |t|
|
44
|
+
t.test_files = FileList['test/**/*.rb']
|
45
|
+
end
|
data/lib/LMG_modbus.rb
ADDED
@@ -0,0 +1,329 @@
|
|
1
|
+
# Required for OLE automation with Microsoft Excel
|
2
|
+
require 'win32ole'
|
3
|
+
require 'rmodbus'
|
4
|
+
|
5
|
+
# Generic Test Class
|
6
|
+
module Test
|
7
|
+
|
8
|
+
puts "Test started..."
|
9
|
+
|
10
|
+
#Constants that define important columns in the spreadsheet
|
11
|
+
|
12
|
+
REGISTER = 'b'
|
13
|
+
RESULT_POS1 = 'k'
|
14
|
+
RESULT_POS2 = 'l'
|
15
|
+
RESULT_POS3 = 'm'
|
16
|
+
RESULT_POS4 = 'n'
|
17
|
+
WRITE_VAL_POS1 = 'g'
|
18
|
+
WRITE_VAL_POS2 = 'h'
|
19
|
+
WRITE_VAL_POS3 = 'i'
|
20
|
+
WRITE_VAL_POS4 = 'j'
|
21
|
+
XLUP = '-4162'
|
22
|
+
|
23
|
+
#Every Test requires a spreadsheet that contains the data that drives the test.
|
24
|
+
#path_to_base_ss is the file location of such a spreadsheet
|
25
|
+
|
26
|
+
def create_test(path_to_base_ss)
|
27
|
+
@base_ss = path_to_base_ss
|
28
|
+
@new_ss = (@base_ss.chomp(".xls")<<'_'<<Time.now.to_a.reverse[5..9].to_s<<(".xls")).gsub('driver','result')
|
29
|
+
@start_time = Time.now
|
30
|
+
@end_time = ''
|
31
|
+
@ss = WIN32OLE::new('excel.Application')
|
32
|
+
@wb = @ss.Workbooks.Open(@base_ss)
|
33
|
+
@wb.SaveAs(@new_ss)
|
34
|
+
@ws = @wb.Worksheets(1)
|
35
|
+
end
|
36
|
+
def run_modbus_write(read_column, write_column)
|
37
|
+
@start_time = Time.now
|
38
|
+
@total_rows = @ws.Range("A65536").End(XLUP).Row
|
39
|
+
@row = 2
|
40
|
+
while (@row <= @total_rows)
|
41
|
+
@inner_row = @row
|
42
|
+
register_value = process_test_case(@ws.Range("#{REGISTER}#{@row}")['Value'],@ws.Range("#{read_column}#{@row}")['Value'] )
|
43
|
+
register_value.each do |s|
|
44
|
+
@ws.Range("#{write_column}#{@inner_row}")['Value'] = s
|
45
|
+
@inner_row += 1
|
46
|
+
end unless register_value.is_a?(NilClass)
|
47
|
+
@ws.Range("#{write_column}#{@inner_row}")['Value'] = $! if register_value == nil
|
48
|
+
@row += 1
|
49
|
+
@wb.Save
|
50
|
+
end
|
51
|
+
@wb.Save
|
52
|
+
@fin = Time.now
|
53
|
+
p @fin
|
54
|
+
@elapsed = (@fin - @start_time)
|
55
|
+
puts " Elapsed time is seconds is: #{@elapsed}"
|
56
|
+
end
|
57
|
+
def open_result
|
58
|
+
@ss.Visible = true
|
59
|
+
@ss.Workbooks.Open(@new_ss)
|
60
|
+
gets
|
61
|
+
end
|
62
|
+
def close
|
63
|
+
@wb.Close
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# This module subtracts 1 from each register address as rmodbus is 1 index based and Liebert is 0 index based.
|
69
|
+
module LMG_Modbus_Client
|
70
|
+
|
71
|
+
def read_coils(addr, ncoils)
|
72
|
+
super(addr-1,ncoils)
|
73
|
+
end
|
74
|
+
def read_discrete_inputs(addr, ncoils)
|
75
|
+
super(addr-1,ncoils)
|
76
|
+
end
|
77
|
+
def read_holding_registers(addr, nreg)
|
78
|
+
super(addr-1,nreg)
|
79
|
+
end
|
80
|
+
def read_input_registers(addr, nreg)
|
81
|
+
begin
|
82
|
+
super(addr-1,nreg)
|
83
|
+
rescue Exception => e
|
84
|
+
e.message
|
85
|
+
end
|
86
|
+
end
|
87
|
+
def write_single_coil(addr, val,delay=1)
|
88
|
+
super(addr-1,val)
|
89
|
+
sleep(delay)
|
90
|
+
end
|
91
|
+
def write_single_register(addr, val, delay=1)
|
92
|
+
begin
|
93
|
+
super(addr-1,val)
|
94
|
+
sleep(delay)
|
95
|
+
rescue Exception => e
|
96
|
+
e.message
|
97
|
+
end
|
98
|
+
end
|
99
|
+
def write_multiple_coils(addr, val, delay=1)
|
100
|
+
super(addr-1,val)
|
101
|
+
sleep(delay)
|
102
|
+
end
|
103
|
+
def write_multiple_registers(addr, val, delay=1)
|
104
|
+
super(addr-1,val)
|
105
|
+
sleep(delay)
|
106
|
+
end
|
107
|
+
def process_test_case(addr,val)
|
108
|
+
register_array = register_type(addr)
|
109
|
+
print "Writing value #{val.to_i} to register #{register_array[0]}..."
|
110
|
+
result = write_single_register(register_array[0].to_i,val.to_i,2)
|
111
|
+
if result.is_a?(Integer) == FALSE then
|
112
|
+
puts "An error occured"
|
113
|
+
return result
|
114
|
+
end
|
115
|
+
result = read_input_registers(register_array[0].to_i,register_array[2].to_i)
|
116
|
+
puts " result of read is #{result}"
|
117
|
+
return result
|
118
|
+
end
|
119
|
+
def read_time_register(addr)
|
120
|
+
epoch = ''
|
121
|
+
self.read_input_registers(addr,2).each do |reg|
|
122
|
+
val = reg.to_i.to_s(2)
|
123
|
+
while val.size < 16
|
124
|
+
val = val.insert(0,'0')
|
125
|
+
end
|
126
|
+
epoch << val
|
127
|
+
end
|
128
|
+
puts Time.at(epoch.to_i(2))
|
129
|
+
end
|
130
|
+
def write_time_register(addr,val)
|
131
|
+
time = val.to_i.to_s(2)
|
132
|
+
string_array = time.split(//) #Convert to an array
|
133
|
+
while string_array.size < 32
|
134
|
+
string_array.unshift(0) ##Pad with leading 0's to make 32 bit
|
135
|
+
end
|
136
|
+
write_multiple_registers(addr, Array[string_array[0..15].join.to_i(2), string_array[16..31].join.to_i(2)])
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
# "Virtual" class that implements most of the nasty stuff
|
142
|
+
module ModbusPoller
|
143
|
+
#Excel constant for the UP arrow - from http://techsupt.winbatch.com/ts/T000001033005F9.html
|
144
|
+
XLUP = -4162
|
145
|
+
include Test
|
146
|
+
private
|
147
|
+
def prepare_test(path_to_base_ss,path_to_modpoll)
|
148
|
+
create_test(path_to_base_ss)
|
149
|
+
@modpoll_workingdir = path_to_modpoll
|
150
|
+
|
151
|
+
# Point to local modpoll dir
|
152
|
+
Dir.chdir(@modpoll_workingdir) # Change working dir to modpoll.exe location
|
153
|
+
print "The working directory for modpoll is: "
|
154
|
+
p Dir.pwd
|
155
|
+
end
|
156
|
+
def run
|
157
|
+
@start_time = Time.now
|
158
|
+
@total_rows = @ws.Range("A65536").End(XLUP).Row
|
159
|
+
@row = 2
|
160
|
+
while (@row <= @total_rows)
|
161
|
+
@inner_row = @row
|
162
|
+
register_value = query_modbus(@ws.Range("b#{@row}")['Value'])
|
163
|
+
if @bit_position != nil then
|
164
|
+
if register_value.to_i == 0 then
|
165
|
+
@ws.Range("g#{@inner_row}")['Value'] = 0
|
166
|
+
else
|
167
|
+
s = "%.16b" % register_value.to_i.abs.to_s(2)
|
168
|
+
@ws.Range("g#{@inner_row}")['Value'] = s[s.size-1-@bit_position.to_i].chr
|
169
|
+
end
|
170
|
+
@inner_row += 1
|
171
|
+
else
|
172
|
+
register_value.split.each do |s|
|
173
|
+
@ws.Range("g#{@inner_row}")['Value'] = s
|
174
|
+
@inner_row += 1
|
175
|
+
end
|
176
|
+
end
|
177
|
+
@row += 1
|
178
|
+
@wb.Save
|
179
|
+
end
|
180
|
+
@wb.Save
|
181
|
+
@wb.Close
|
182
|
+
@fin = Time.now
|
183
|
+
p @fin
|
184
|
+
@elapsed = (@fin - @start_time)
|
185
|
+
puts " Elapsed time is seconds is: #{@elapsed}"
|
186
|
+
end
|
187
|
+
def query_modbus(register)
|
188
|
+
count = 1
|
189
|
+
bit_count = 0
|
190
|
+
starting_register_and_type = register_type(register)
|
191
|
+
starting_register = starting_register_and_type[0]
|
192
|
+
|
193
|
+
#Determine the appropriate count of registers to query.
|
194
|
+
while (@row <= @total_rows)
|
195
|
+
|
196
|
+
current_register_and_type = register_type(@ws.Range("b#{@row}")['Value'])
|
197
|
+
current_register = current_register_and_type[0]
|
198
|
+
type = current_register_and_type[1]
|
199
|
+
current_register_size = current_register_and_type[2]
|
200
|
+
@bit_position = current_register_and_type[3]
|
201
|
+
|
202
|
+
if @row < @total_rows
|
203
|
+
next_register_and_type = register_type(@ws.Range("b#{@row+1}")['Value'])
|
204
|
+
next_register = next_register_and_type[0]
|
205
|
+
next_register_size = next_register_and_type[2]
|
206
|
+
next_bit_position = next_register_and_type[3]
|
207
|
+
end
|
208
|
+
|
209
|
+
if @bit_position == nil and next_bit_position == nil then
|
210
|
+
if(current_register_size == next_register_size and
|
211
|
+
next_register.to_i == current_register.to_i + current_register_size.to_i and
|
212
|
+
count < 99) then
|
213
|
+
count += 1
|
214
|
+
@row += 1
|
215
|
+
else break
|
216
|
+
end
|
217
|
+
else break
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
#Build and pass the commands to the OS
|
222
|
+
if (@row <= @total_rows) then
|
223
|
+
begin
|
224
|
+
command = @base_cmd + count.to_s + ' -a ' + @slave_addr.to_s + type + ' -r ' + starting_register + ' ' + @target
|
225
|
+
rescue TypeError
|
226
|
+
puts 'An error occured, is the driver spreadsheet in the proper format?'
|
227
|
+
return 'Error!'
|
228
|
+
end
|
229
|
+
puts"command = #{command}"
|
230
|
+
modbus_values =''
|
231
|
+
modbus_data = `#{command}`.each do |s|
|
232
|
+
if s =~ /^\[/ #If stdout line starts with a '['
|
233
|
+
array = s.split(/ /)
|
234
|
+
modbus_values << array[1]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
return modbus_values
|
238
|
+
end
|
239
|
+
end
|
240
|
+
def register_type(register)
|
241
|
+
#Determine the type of register.
|
242
|
+
|
243
|
+
type = case register
|
244
|
+
when /^3.*1\)$/ then ' -t 3' #16 bit input register
|
245
|
+
when /^3.*2\)$/ then ' -t 3:int -i' #32 bit input register - Date/Time fields need the -i switch (Big Endian)
|
246
|
+
when /^3.*20\)$/ then ' -t 3' #16 bit input register
|
247
|
+
when /^4.*1\)$/ then ' -t 4' #16 bit holding register
|
248
|
+
when /^4.*2\)$/ then ' -t 4:int -i' #32 bit holding register - Date/Time fields need the -i switch (Big Endian)
|
249
|
+
when /^4.*1\).*Bit.*/ then type = ' -t 4'
|
250
|
+
when /^1/ then ' -t 1' #Discrete input register
|
251
|
+
end
|
252
|
+
|
253
|
+
type = ' -t 0' if register.length() < 8
|
254
|
+
#Cleanup the register value
|
255
|
+
|
256
|
+
register_size = register.slice(/\(.*\)/) #Match the parenthetical portion of the string (size)
|
257
|
+
bit_position = register.slice(/ - Bit.*$/) #Match the Bit specification
|
258
|
+
register_value = register.gsub(/\(.*\)/,'') #Remove the parenthetical portion of the string
|
259
|
+
register_value = register_value.gsub(/^./,'') unless type == ' -t 0' #Remove the first character
|
260
|
+
register_value = register_value.gsub(/^0+/,'') #Remove any leading zeros
|
261
|
+
register_value = register_value.gsub(/ - Bit.*/,'') #Remove the - Bit specification
|
262
|
+
|
263
|
+
#Build an array of register number type and size
|
264
|
+
|
265
|
+
return_value = Array.new
|
266
|
+
bit_position = bit_position.gsub(' - Bit ','') unless bit_position == nil
|
267
|
+
return_value[0,3] = [register_value, type, register_size.tr('()',''),bit_position]
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
# Class to poll modbus TCP interfaces
|
273
|
+
class ModbusPollerTCP
|
274
|
+
include ModbusPoller
|
275
|
+
# slave_addr:: the address of the modpoll server.
|
276
|
+
# ip:: the ip address of the modpoll server.
|
277
|
+
# path_to_base_ss:: the spreadsheet that contains the modbus datapoints to poll.
|
278
|
+
# path_to_modpoll:: the path to the modpoll executable.
|
279
|
+
# encapsulation:: whether or not to use encapsulated RTU over TCP
|
280
|
+
def initialize(slave_addr, ip,path_to_base_ss,path_to_modpoll,encapsulation)
|
281
|
+
|
282
|
+
prepare_test(path_to_base_ss,path_to_modpoll)
|
283
|
+
case encapsulation
|
284
|
+
when 'enc' then @base_cmd = 'modpoll -o 5.0 -p 8002 -1 -m enc -c '
|
285
|
+
else @base_cmd = 'modpoll -o 5.0 -1 -m tcp -c ' #TODO This should get more specific or use an optional argument
|
286
|
+
end
|
287
|
+
|
288
|
+
@slave_addr = slave_addr
|
289
|
+
@target = ip
|
290
|
+
end
|
291
|
+
# Call run to begin the test
|
292
|
+
def run
|
293
|
+
super
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Class to poll modbus 485 interfaces
|
298
|
+
class ModbusPoller485
|
299
|
+
include ModbusPoller
|
300
|
+
# slave_addr:: the address of the modpoll server.
|
301
|
+
# com_port:: the serial interface attached to the modpoll server (e.g. com1, com2, com3...).
|
302
|
+
# path_to_base_ss:: the spreadsheet that contains the modbus datapoints to poll.
|
303
|
+
# path_to_modpoll:: the path to the modpoll executable.
|
304
|
+
def initialize(slave_addr, com_port,path_to_base_ss,path_to_modpoll)
|
305
|
+
prepare_test(path_to_base_ss,path_to_modpoll)
|
306
|
+
@base_cmd = 'modpoll -o 5.0 -1 -p none -c '
|
307
|
+
@slave_addr = slave_addr
|
308
|
+
@target = com_port
|
309
|
+
end
|
310
|
+
# Call run to begin the test
|
311
|
+
def run
|
312
|
+
super
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Class to work with Modbus485 (RTU)
|
317
|
+
class Modbus485 < ModBus::RTUClient
|
318
|
+
include LMG_Modbus_Client
|
319
|
+
include Test
|
320
|
+
include ModbusPoller
|
321
|
+
end
|
322
|
+
|
323
|
+
# Class to work with ModbusTCP
|
324
|
+
class ModbusTCP < ModBus::TCPClient
|
325
|
+
include LMG_Modbus_Client
|
326
|
+
include Test
|
327
|
+
include ModbusPoller
|
328
|
+
end
|
329
|
+
|
data/lib/example.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'LMG_modbus'
|
2
|
+
|
3
|
+
# Constant for the number of seconds in a year from http://en.wikipedia.org/wiki/Year
|
4
|
+
YEAR = 31557600
|
5
|
+
DAY = 86400
|
6
|
+
|
7
|
+
# Constants for columns in the test driver spreadsheet
|
8
|
+
WRITE_DEFAULT = 'G'
|
9
|
+
WRITE_POSITIVE = 'H'
|
10
|
+
WRITE_NEGATIVE = 'I'
|
11
|
+
RESULT_DEFAULT = 'K'
|
12
|
+
RESULT_POSITIVE = 'L'
|
13
|
+
RESULT_NEGATIVE = 'M'
|
14
|
+
|
15
|
+
## Example - Write register test 485
|
16
|
+
|
17
|
+
#test = Modbus485.new('com1')
|
18
|
+
#test.create_test('C:\Documents and Settings\shanksstemp\My Documents\Testing\V4Cooling2\CR\CRV_FDM_462_write_driver.xls')
|
19
|
+
#puts "Running Positive Tests"
|
20
|
+
#test.run_modbus_write(WRITE_POSITIVE,RESULT_POSITIVE)
|
21
|
+
#puts "Running Negative Tests"
|
22
|
+
#test.run_modbus_write(WRITE_NEGATIVE,RESULT_NEGATIVE)
|
23
|
+
#puts "Restoring Default"
|
24
|
+
#test.run_modbus_write(WRITE_DEFAULT,RESULT_DEFAULT) #default
|
25
|
+
#test.close
|
26
|
+
|
27
|
+
# Example - Write register test TCP
|
28
|
+
#
|
29
|
+
#test = ModbusTCP.new('126.4.202.199')
|
30
|
+
#test.create_test('C:\Documents and Settings\shanksstemp\Desktop\PA\DS\LiebertDS_FDM_35_writedriver.xls')
|
31
|
+
#test.run_modbus_write('g','k')
|
32
|
+
#test.close
|
33
|
+
|
34
|
+
# Example - Static Test using modpoll
|
35
|
+
|
36
|
+
#test = ModbusPoller485.new(1, 'com1','C:\Documents and Settings\shanksstemp\My Documents\Testing\V4Cooling2\CR\CRV_FDM_462.xls','c:\win32')
|
37
|
+
#test.run
|
38
|
+
|
39
|
+
#Example - Read time and set time
|
40
|
+
test = Modbus485.new('com1')
|
41
|
+
#test.read_time_register(258)
|
42
|
+
#test.read_time_register(260)
|
43
|
+
#test.read_time_register(265)
|
44
|
+
test.read_time_register(9998)
|
45
|
+
|
46
|
+
test.write_time_register(9998,Time.now-30*YEAR)
|
47
|
+
sleep(60)
|
48
|
+
test.read_time_register(9998)
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: LMG_modbus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 1.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- ""
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-02 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Useful set of test automation classes for bulk polling and bulk writing modbus registers
|
23
|
+
email: ""
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README
|
30
|
+
- LICENSE
|
31
|
+
files:
|
32
|
+
- LICENSE
|
33
|
+
- README
|
34
|
+
- Rakefile
|
35
|
+
- lib/example.rb
|
36
|
+
- lib/LMG_modbus.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage:
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
hash: 3
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.7
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Useful set of test automation classes for bulk polling and bulk writing modbus registers
|
71
|
+
test_files: []
|
72
|
+
|