LMG_modbus 1.0.2

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