LMG_modbus 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/LICENSE +3 -0
  2. data/README +33 -0
  3. data/Rakefile +45 -0
  4. data/lib/LMG_modbus.rb +329 -0
  5. data/lib/example.rb +48 -0
  6. metadata +72 -0
data/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ == LMG_modbus
2
+
3
+ Put appropriate LICENSE for your project here.
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
+