tallakt-plcutil 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *swp
data/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.0.4 2009-06-12
2
+
3
+ * 1 minor enhancement
4
+ * Ncurses based resizing to full screen intouchpp
5
+
6
+ == 0.0.3 2009-06-12
7
+
8
+ * Lots of work and progress - intouchpp should just work by now, also s7tointouch
9
+
1
10
  == 0.0.1 2009-01-28
2
11
 
3
12
  * 1 major enhancement:
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Tallak Tveide
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt CHANGED
@@ -4,18 +4,33 @@ Manifest.txt
4
4
  README.rdoc
5
5
  Rakefile
6
6
  TODO
7
+ bin
7
8
  bin/awlpp
9
+ bin/intouchpp
8
10
  bin/s7tointouch
11
+ lib
12
+ lib/plcutil
13
+ lib/plcutil/siemens
9
14
  lib/plcutil/siemens/awlfile.rb
10
- lib/plcutil/siemens/awlprettyprintrunner.rb
11
- lib/plcutil/siemens/step7tointouchrunner.rb
15
+ lib/plcutil/siemens/awl_pretty_print_runner.rb
16
+ lib/plcutil/siemens/sdffile.rb
17
+ lib/plcutil/siemens/step7_to_intouch_runner.rb
18
+ lib/plcutil/wonderware
12
19
  lib/plcutil/wonderware/intouchfile.rb
13
- lib/plcutil/wonderware/intouchreader.rb
20
+ lib/plcutil/wonderware/intouch_pretty_print_runner.rb
14
21
  lib/plcutil/wonderware/standard_sections.yaml
15
22
  lib/plcutil.rb
23
+ plcutil.gemspec
24
+ script
16
25
  script/console
17
26
  script/console.cmd
18
27
  script/destroy
19
28
  script/destroy.cmd
20
29
  script/generate
21
30
  script/generate.cmd
31
+ test
32
+ test/input_files
33
+ test/input_files/step7_v5.4
34
+ test/input_files/step7_v5.4/00000001.AWL
35
+ test/input_files/step7_v5.4/S7_pro2.zip
36
+ test/input_files/step7_v5.4/SYMLIST.DBF
data/README.rdoc CHANGED
@@ -32,7 +32,8 @@ The commands support the --help parameter for a full list of parameters
32
32
 
33
33
  == INSTALL:
34
34
 
35
- gem install plcutil
35
+ sudo gem sources -a http://gems.github.com
36
+ sudo gem install tallakt-plcutil
36
37
 
37
38
 
38
39
  == LICENSE:
@@ -59,3 +60,4 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
59
60
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
60
61
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
61
62
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
63
+
data/Rakefile CHANGED
@@ -1,27 +1,52 @@
1
- %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
- require File.dirname(__FILE__) + '/lib/plcutil'
3
-
4
- # Generate all the Rake tasks
5
- # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
- $hoe = Hoe.new('plcutil', Plcutil::VERSION) do |p|
7
- p.developer('Tallak Tveide', 'tallak@tveide.net')
8
- p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
- p.rubyforge_name = p.name # TODO this is default value
10
- # p.extra_deps = [
11
- # ['activesupport','>= 2.0.2'],
12
- # ]
13
- p.extra_dev_deps = [
14
- ['newgem', ">= #{::Newgem::VERSION}"]
15
- ]
16
-
17
- p.clean_globs |= %w[**/.DS_Store tmp *.log]
18
- path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
19
- p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
20
- p.rsync_args = '-av --delete --ignore-errors'
21
- end
22
-
23
- require 'newgem/tasks' # load /tasks/*.rake
24
- Dir['tasks/**/*.rake'].each { |t| load t }
25
-
26
- # TODO - want other tests/tasks run by default? Add them to the list
27
- # task :default => [:spec, :features]
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "tallakt-plcutil"
8
+ gem.summary = %Q{Ruby PLC file library}
9
+ gem.description = %Q{Ruby library for using Siemens and Intouch files}
10
+ gem.email = "tallak@tveide.net"
11
+ gem.homepage = "http://github.com/tallakt/tallakt-plcutil"
12
+ gem.authors = ["Tallak Tveide"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "tallakt-plcutil #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/awlpp CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
- require 'plcutil/siemens/awlprettyprintrunner'
4
- require 'plcutil/siemens/step7tointouchrunner'
3
+ require 'plcutil/siemens/awl_pretty_print_runner'
5
4
 
6
5
  # try: awlpp --help
7
6
  PlcUtil::AwlPrettyPrintRunner.new ARGV
data/bin/intouchpp ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'plcutil/wonderware/intouch_pretty_print_runner'
4
+
5
+ # try: intouchpp --help
6
+ PlcUtil::IntouchPrettyPrintRunner.new ARGV
7
+
data/bin/s7tointouch CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
- require 'plcutil/siemens/step7tointouchrunner'
3
+ require 'plcutil/siemens/step7_to_intouch_runner'
4
4
 
5
5
  # try: s7tointouch --help
6
6
  PlcUtil::Step7ToIntouchRunner.new ARGV
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
3
  require 'optparse'
4
- require 'plcutil/siemens/awlfile'
4
+ require 'tallakt-plcutil/siemens/awlfile'
5
5
 
6
6
  module PlcUtil
7
7
  # Command line tool to read and output an awl file
@@ -12,6 +12,8 @@ module PlcUtil
12
12
  @commentformat = '# %s / %s'
13
13
  @output = nil
14
14
  @symlistfile = nil
15
+ @no_block = false
16
+
15
17
  option_parser.parse! args
16
18
  if args.size != 1
17
19
  show_help
@@ -28,12 +30,19 @@ module PlcUtil
28
30
  end
29
31
  end
30
32
 
33
+ def fix_name(name)
34
+ if @no_block
35
+ name.sub /^[^\.]*\./, ''
36
+ else
37
+ name
38
+ end
39
+ end
31
40
 
32
41
  def print_to_file(f)
33
- @awl.each_tag do |name, addr, comment, struct_comment, type|
42
+ @awl.each_tag do |name, data_block_name, addr, comment, struct_comment, type|
34
43
  f.puts @format % [
35
44
  addr,
36
- name,
45
+ fix_name(name),
37
46
  type.to_s,
38
47
  [comment, struct_comment].compact! ? '' : (@commentformat % [comment, struct_comment])
39
48
  ]
@@ -47,7 +56,10 @@ module PlcUtil
47
56
  format = '%s;%s;%s;%s'
48
57
  @commentformat = '%s'
49
58
  end
50
- opts.on("-s", "--symlist FILE", String, "Specify SYMLIST.DBF file from S7 project ") do |symlistfile|
59
+ opts.on("-n", "--no-block", String, "Dont use the datablock as part of the tag", "name") do
60
+ @no_block = true
61
+ end
62
+ opts.on("-s", "--symlist FILE", String, "Specify SYMLIST.DBF file from S7 project ") do |symlistfile|
51
63
  @awloptions[:symlist] = symlistfile
52
64
  end
53
65
  opts.on("-b", "--block NAME=ADDR", String, "Define address of datablock without", "reading symlist") do |blockdef|
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+
5
+ module PlcUtil
6
+
7
+ # Reads a Siemens Step 7 AWL file and creates single tags with addresses and comment
8
+ class AwlFile
9
+ attr_reader :symlist
10
+
11
+ def initialize(filename, options = {})
12
+ @types = {}
13
+ init_basic_types
14
+ @datablocks = []
15
+ @symlist = {}
16
+
17
+ if options[:symlist]
18
+ require 'dbf' or raise 'Please install gem dbf to read symlist file'
19
+
20
+ raise 'Specified symlist file not found' unless File.exists? options[:symlist]
21
+ table = DBF::Table.new(options[:symlist])
22
+ table.records.each do |rec|
23
+ @symlist[rec.attributes['_skz']] = rec.attributes['_opiec'] # or _ophist or _datatyp
24
+ end
25
+ end
26
+
27
+ @symlist.merge!(options[:blocks] || {}) # User may override blocks
28
+
29
+ File.open filename do |f|
30
+ parse f
31
+ end
32
+ end
33
+
34
+ def each_tag(options = {})
35
+ @datablocks.each do |var|
36
+ name = var.name
37
+ if options[:no_block]
38
+ name = nil
39
+ end
40
+ var.type.explode(Address.new(0, data_block_address(var.name)), name, options).each do |item|
41
+ ii = item.clone
42
+ ii[:type] = item[:type].downcase.to_sym
43
+ yield ii
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def data_block_address(name)
51
+ if @symlist.key? name
52
+ @symlist[name].gsub /\s+/, ''
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def init_basic_types
59
+ types = {
60
+ 'BOOL' => 1,
61
+ 'BYTE' => 8,
62
+ 'CHAR' => 8,
63
+ 'DATE' => 2 * 8,
64
+ 'DINT' => 4 * 8,
65
+ 'DWORD' => 4 * 8,
66
+ 'INT' => 2 * 8,
67
+ 'REAL' => 4 * 8,
68
+ 'S5TIME' => 2 * 8,
69
+ 'TIME' => 4 * 8,
70
+ 'TIME_OF_DAY' => 4 * 8,
71
+ 'WORD' => 2 * 8,
72
+ 'DATE_AND_TIME' => 4 * 8, # ??? only used in VAR_TEMP
73
+ 'TIMER' => 1, # Only in FC calls
74
+ 'CONT_C' => 125 * 8,
75
+ }
76
+ types.each {|name, size| add_type BasicType.new(name, size) }
77
+ end
78
+
79
+ def add_type(type)
80
+ @types[type.name] = type
81
+ end
82
+
83
+ def lookup_type(type)
84
+ raise "Could not find type '#{type}'" unless @types.key? type
85
+ @types[type]
86
+ end
87
+
88
+ def parse(file)
89
+ stack = []
90
+ in_array_decl = false
91
+ in_datablock_decl = false
92
+ tagname = start = stop = type = comment = nil
93
+ db_to_fb = {}
94
+ file.each_line do |l|
95
+ l.chomp!
96
+ if in_array_decl
97
+ if l.match '^\s+\"?([A-Za-z0-9_]+)"?\s?;'
98
+ type = $1
99
+ stack.last.add Variable.new(tagname, ArrayType.new(@types[type], start..stop), comment)
100
+ end
101
+ in_array_decl = false
102
+ else
103
+ in_array_decl = false
104
+ case l
105
+ # TODO should also cater for 'DB 90' type addresses
106
+ when /^TYPE "(.+?)"/
107
+ stack = [StructType.new $1, :datatype]
108
+ add_type stack.last
109
+ when /^FUNCTION_BLOCK "(.+?)"/
110
+ stack = [StructType.new $1, :functionblock]
111
+ add_type stack.last
112
+ when /^DATA_BLOCK ("(.+?)"|DB\s+(\d+))/
113
+ in_datablock_decl = true
114
+ name = $2 || ('DB' + $3)
115
+ stack = [StructType.new name, :datablock]
116
+ @datablocks << Variable.new(name, stack.last)
117
+ when /^VAR_TEMP/
118
+ s = StructType.new 'VAR_TEMP', :anonymous
119
+ stack = [s]
120
+ when /^\s*(\S+) : STRUCT /
121
+ in_datablock_decl = false
122
+ s = StructType.new 'STRUCT', :anonymous
123
+ stack.last.add Variable.new $1, s
124
+ stack << stack.last.children.last.type
125
+ when /^BEGIN$/
126
+ in_datablock_decl = false
127
+ when /^\s+BEGIN/
128
+ stack.pop
129
+ # New variable in struct or data block
130
+ when /^\s+([A-Za-z0-9_ ]+) : "?([A-Za-z0-9_ ]+?)"?\s*(:=\s*[^;]+)?;(\s*\/\/(.*))?/
131
+ if stack.any?
132
+ tagname, type_name, comment = $1, $2, ($5 || '')
133
+ stack.last.add Variable.new(tagname, lookup_type(type_name), comment)
134
+ end
135
+ when /^\s+([A-Za-z0-9_]+) : ARRAY\s*\[(\d+)\D+(\d+) \] OF "?([A-Za-z0-9_]+)"?\s?;(\s*\/\/(.*))?/
136
+ tagname, start, stop, type, comment = $1, $2, $3, $4, $6
137
+ stack.last.add Variable.new(tagname, ArrayType.new(lookup_type(type), start..stop), comment)
138
+ when /^\s+([A-Za-z0-9_]+) : ARRAY\s*\[(\d+)\D+(\d+) \] OF(\s?\/\/(.*))?$/
139
+ tagname, start, stop, comment = $1, $2, $3, $4
140
+ in_array_decl = true
141
+ when /^"(.*?)"$/
142
+ # datablock definition is stored in FB definition
143
+ # remember which FB to use but assign the FB
144
+ # only after all blocks have been loaded
145
+ db_to_fb[stack.first.name] = $1 if in_datablock_decl
146
+ end
147
+ end
148
+ end
149
+
150
+ # Update FB to DB use
151
+ @datablocks.each do |db|
152
+ if db_to_fb.key? db.name
153
+ fb = lookup_type db_to_fb[db.name]
154
+ fb.children.each {|child| db.type.add child } if fb.respond_to? :children
155
+ end
156
+ end
157
+ end
158
+
159
+ def defined_type(type)
160
+ @types.key?(type.name)
161
+ end
162
+
163
+
164
+ class BasicType
165
+ attr_accessor :bit_size, :name
166
+
167
+ def initialize(name, bit_size)
168
+ @bit_size, @name = bit_size, name
169
+ end
170
+
171
+ def explode(start_addr, name, comment, struct_comment, options = {})
172
+ actual_start = start_addr.next_start bit_size
173
+ [{:addr => actual_start, :name => name,
174
+ :struct_comment => struct_comment, :comment => comment, :type => @name}]
175
+ end
176
+
177
+ def end_address(start_address)
178
+ start_address.next_start(bit_size).skip! bit_size
179
+ end
180
+ end
181
+
182
+
183
+
184
+ class StructType
185
+ attr_accessor :name, :children, :type
186
+
187
+ def initialize(name = 'STRUCT', type = :anonymous)
188
+ @name = name
189
+ @children = []
190
+ @type = type
191
+ end
192
+
193
+ def add(child)
194
+ raise 'Added nil child' unless child
195
+ raise 'Added nil child type' unless child.type
196
+ @children << child
197
+ end
198
+
199
+ def end_address(start_address)
200
+ addr = start_address.next_start
201
+ @children.each do |child|
202
+ addr = child.type.end_address addr
203
+ end
204
+ addr.next_start!
205
+ end
206
+
207
+ def explode(start_addr, name, comment = nil, struct_comment = nil, options = {})
208
+ addr = start_addr.next_start
209
+ exploded = []
210
+ @children.each do |child|
211
+
212
+ exploded += child.type.explode(addr, [name, child.name].compact.join('.'), child.comment,
213
+ type == :datablock ? child.comment : struct_comment, options)
214
+ addr = child.type.end_address addr
215
+ end
216
+ exploded
217
+ end
218
+ end
219
+
220
+ class ArrayType
221
+ attr_accessor :range, :type
222
+
223
+ def initialize(type, range)
224
+ raise 'Added nil array type' unless type
225
+ raise 'Added nil array range' unless range
226
+ @range, @type = range, type
227
+ end
228
+
229
+ def name
230
+ 'ARRAY'
231
+ end
232
+
233
+ def end_address(start_address)
234
+ addr = start_address.next_start
235
+ range.each do
236
+ addr = type.end_address addr
237
+ end
238
+ addr
239
+ end
240
+
241
+ def explode(start_addr, name, comment, struct_comment, options = {})
242
+ exploded = []
243
+ addr = start_addr.next_start
244
+ range.to_a.each_with_index do |v, i|
245
+ exploded += type.explode(addr, name + '[' + v.to_s + ']', comment, struct_comment, options = {})
246
+ addr = type.end_address(addr)
247
+ end
248
+ exploded
249
+ end
250
+ end
251
+
252
+ class Variable
253
+ attr_accessor :name, :type, :comment
254
+
255
+ def initialize(name, type, comment = nil)
256
+ @name, @type, @comment= name, type, comment
257
+ end
258
+ end
259
+
260
+ class Address
261
+ attr_accessor :bit_addr, :data_block_addr
262
+
263
+ def initialize(bit_addr, data_block_addr = nil)
264
+ @bit_addr, @data_block_addr = bit_addr, data_block_addr
265
+ end
266
+
267
+ def to_s
268
+ (data_block_addr || 'DB???') + ',' + byte.to_s + '.' + bit.to_s
269
+ end
270
+
271
+ def bit
272
+ @bit_addr % 8
273
+ end
274
+
275
+ def byte
276
+ @bit_addr / 8
277
+ end
278
+
279
+ def next_start!(bit_size = 2 * 8)
280
+ bs = [bit_size, 2 * 8].min # bools use bits, chars/bytes one byte others two byte rounding of start address
281
+ rest = @bit_addr % bs
282
+ @bit_addr += (bs - rest) if rest > 0
283
+ self
284
+ end
285
+
286
+ def next_start(bit_size = 2 * 8)
287
+ self.clone.next_start! bit_size
288
+ end
289
+
290
+ def skip!(bit_count)
291
+ @bit_addr += bit_count
292
+ self
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ostruct'
4
+
5
+ module PlcUtil
6
+ class SdfFile
7
+ attr_reader :tags
8
+
9
+ def initialize(filename = nil)
10
+ @tags = []
11
+ case filename
12
+ when '--'
13
+ parse $stdin
14
+ when nil
15
+ # dont read file
16
+ else
17
+ File.open filename do |f|
18
+ parse f
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ private
25
+
26
+ def parse(f)
27
+ f.each_line do |l|
28
+ item = {}
29
+ values = l.strip.split(/,/).map do |x|
30
+ x.gsub(/^"|"$/, '').strip
31
+ end
32
+ item = Hash[[:name, :addr, :type, :comment].zip values]
33
+ item[:addr].gsub! /\s+/, ' '
34
+ if not item[:type].match(/\s/) # skip OB XX, FC XX, FB XX and so on
35
+ begin
36
+ item[:type] = item[:type].downcase.to_sym
37
+ @tags << item
38
+ rescue
39
+ # no worries
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+