tallakt-plcutil 0.2.3 → 0.2.4
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.
- data/History.txt +6 -0
- data/README.rdoc +2 -1
- data/bin/awlpp +3 -4
- data/bin/intouchpp +2 -2
- data/bin/pl7tointouch +2 -2
- data/bin/s7tointouch +2 -2
- data/lib/{tallakt-plcutil → plcutil}/schneider/pl7_to_intouch_runner.rb +3 -12
- data/lib/plcutil/siemens/awl/array_type.rb +21 -0
- data/lib/plcutil/siemens/awl/awl.treetop +172 -0
- data/lib/plcutil/siemens/awl/awl_file.rb +91 -0
- data/lib/plcutil/siemens/awl/basic_type.rb +52 -0
- data/lib/plcutil/siemens/awl/db_address.rb +52 -0
- data/lib/plcutil/siemens/awl/struct_type.rb +52 -0
- data/lib/plcutil/siemens/awl/symlist_file.rb +28 -0
- data/lib/plcutil/siemens/awl/treetop_nodes.rb +110 -0
- data/lib/plcutil/siemens/awl_pretty_print_runner.rb +48 -0
- data/lib/{tallakt-plcutil → plcutil}/siemens/sdffile.rb +0 -0
- data/lib/{tallakt-plcutil → plcutil}/siemens/step7_to_intouch_runner.rb +3 -3
- data/lib/{tallakt-plcutil → plcutil}/version.rb +1 -1
- data/lib/{tallakt-plcutil → plcutil}/wonderware/intouch_pretty_print_runner.rb +1 -1
- data/lib/{tallakt-plcutil → plcutil}/wonderware/intouchfile.rb +0 -0
- data/lib/{tallakt-plcutil → plcutil}/wonderware/standard_sections.yaml +0 -0
- data/lib/{tallakt-plcutil.rb → plcutil.rb} +1 -1
- metadata +76 -18
- data/Rakefile +0 -36
- data/lib/tallakt-plcutil/siemens/awl_pretty_print_runner.rb +0 -87
- data/lib/tallakt-plcutil/siemens/awlfile.rb +0 -295
data/History.txt
CHANGED
data/README.rdoc
CHANGED
|
@@ -8,7 +8,8 @@ A set of command line utilities and helper classes to convert between IO list fi
|
|
|
8
8
|
|
|
9
9
|
Currently Siemens, Schneider and Wonderware are supported, Schneider and Wonderware IAS support planned in the near future.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The utilities in this gem are somewhat rough quality, no guarantees are given
|
|
12
|
+
wrt correct operation. Even so, I hope they might be useful.
|
|
12
13
|
|
|
13
14
|
== FEATURES/PROBLEMS:
|
|
14
15
|
|
data/bin/awlpp
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require '
|
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
|
3
|
+
require 'plcutil/siemens/awl_pretty_print_runner'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
PlcUtil::AwlPrettyPrintRunner.new ARGV
|
|
5
|
+
PlcUtil::AwlPrettyPrintRunner.run
|
|
7
6
|
|
data/bin/intouchpp
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require '
|
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
|
3
|
+
require 'plcutil/wonderware/intouch_pretty_print_runner'
|
|
4
4
|
|
|
5
5
|
# try: intouchpp --help
|
|
6
6
|
PlcUtil::IntouchPrettyPrintRunner.new ARGV
|
data/bin/pl7tointouch
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require '
|
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
|
3
|
+
require 'plcutil/schneider/pl7_to_intouch_runner'
|
|
4
4
|
|
|
5
5
|
# try: pl7tointouch --help
|
|
6
6
|
PlcUtil::PL7ToIntouchRunner.new ARGV
|
data/bin/s7tointouch
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require '
|
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
|
3
|
+
require 'plcutil/siemens/step7_to_intouch_runner'
|
|
4
4
|
|
|
5
5
|
# try: s7tointouch --help
|
|
6
6
|
PlcUtil::Step7ToIntouchRunner.new ARGV
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#encoding: utf-8
|
|
3
3
|
|
|
4
4
|
require 'optparse'
|
|
5
|
-
require '
|
|
5
|
+
require 'plcutil/wonderware/intouchfile'
|
|
6
6
|
require 'ostruct'
|
|
7
7
|
|
|
8
8
|
module PlcUtil
|
|
@@ -46,15 +46,10 @@ module PlcUtil
|
|
|
46
46
|
|
|
47
47
|
def read_pl7_file(io)
|
|
48
48
|
io.each_line do |l|
|
|
49
|
-
mres = l.match(/^%M(W
|
|
49
|
+
mres = l.match(/^%M(W)?(\d+)(:X(\d+))?\t(\S+)\t(\S+)(\t\"?([^\t"]*))?/)
|
|
50
50
|
if mres
|
|
51
51
|
item = OpenStruct.new
|
|
52
|
-
|
|
53
|
-
when 'W'
|
|
54
|
-
item.is_word = true
|
|
55
|
-
when 'F'
|
|
56
|
-
item.is_float = true
|
|
57
|
-
end
|
|
52
|
+
item.is_word = mres[1]
|
|
58
53
|
item.index = mres[2].to_i
|
|
59
54
|
item.bit_index = mres[4] && mres[4].to_i
|
|
60
55
|
item.tagname = mres[5]
|
|
@@ -88,10 +83,6 @@ module PlcUtil
|
|
|
88
83
|
@intouchfile.new_io_int(tag, data) do |io|
|
|
89
84
|
io.item_name = '%d S' % (item.index + 400001)
|
|
90
85
|
end
|
|
91
|
-
elsif item.is_float
|
|
92
|
-
@intouchfile.new_io_real(tag, data) do |io|
|
|
93
|
-
io.item_name = '%d F' % (item.index + 400001)
|
|
94
|
-
end
|
|
95
86
|
else
|
|
96
87
|
@intouchfile.new_io_disc(tag, data) do |io|
|
|
97
88
|
io.item_name = (item.index + 1).to_s
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module PlcUtil
|
|
2
|
+
module Awl
|
|
3
|
+
class ArrayType
|
|
4
|
+
attr_accessor :range, :element_type
|
|
5
|
+
|
|
6
|
+
def initialize(element_type, range)
|
|
7
|
+
@range, @element_type = range, element_type
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def skip_padding(addr)
|
|
11
|
+
addr.next_word
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def end_address(start_addr)
|
|
15
|
+
start_addr.skip type.bit_size * range.count
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
module PlcUtil
|
|
2
|
+
module Awl
|
|
3
|
+
grammar AwlGrammar
|
|
4
|
+
rule root
|
|
5
|
+
toplevel <TopLevelNode>
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
rule toplevel
|
|
9
|
+
(udt <UdtNode> / db <DbNode> / ob <ObNode>)*
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rule db
|
|
13
|
+
'DATA_BLOCK ' name eol
|
|
14
|
+
title
|
|
15
|
+
'VERSION : ' version eol
|
|
16
|
+
eol*
|
|
17
|
+
root_struct_decl
|
|
18
|
+
'BEGIN' eol
|
|
19
|
+
assignment*
|
|
20
|
+
'END_DATA_BLOCK' eol
|
|
21
|
+
ws_eol
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
rule udt
|
|
25
|
+
'TYPE ' name eol
|
|
26
|
+
'VERSION : ' version eol
|
|
27
|
+
eol*
|
|
28
|
+
root_struct_decl
|
|
29
|
+
'END_TYPE' eol
|
|
30
|
+
ws_eol
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
rule ob
|
|
34
|
+
'ORGANIZATION_BLOCK ' name eol
|
|
35
|
+
title
|
|
36
|
+
'VERSION : ' version eol
|
|
37
|
+
eol*
|
|
38
|
+
(!ob_end .)*
|
|
39
|
+
ob_end eol
|
|
40
|
+
ws_eol
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
rule name
|
|
44
|
+
quoted_string <QuotedNameNode> / db_name <DbNameNode>
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
rule quoted_string
|
|
48
|
+
'"' v:(!'"' .)* '"'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
rule single_quoted_string
|
|
52
|
+
single_quote (!single_quote .)* single_quote
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
rule single_quote
|
|
56
|
+
"'"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
rule db_name
|
|
60
|
+
'DB ' number:([0-9]+)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
rule version
|
|
64
|
+
[0-9]+ '.' [0-9]+
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
rule title
|
|
68
|
+
'TITLE =' optional_title:(ws title_quoted:(quoted_string))? eol
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
rule ob_end
|
|
72
|
+
'END_ORGANIZATION_BLOCK'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
rule assignment
|
|
76
|
+
ws identifier ws ':=' ws value ';' ws eol
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
rule declaration
|
|
80
|
+
ws
|
|
81
|
+
identifier
|
|
82
|
+
' : '
|
|
83
|
+
d:(array_declaration <ArrayDeclarationNode> / nonarray_declaration <NonArrayDeclarationNode>)
|
|
84
|
+
eol
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
rule nonarray_declaration
|
|
88
|
+
non_array_data_type
|
|
89
|
+
ws?
|
|
90
|
+
initial_value? ws?
|
|
91
|
+
';'
|
|
92
|
+
ws? comment:(line_comment?)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
rule initial_value
|
|
96
|
+
':=' ws single_quoted_string
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
rule array_declaration
|
|
100
|
+
'ARRAY [' ar_begin:([0-9]+) ' .. ' ar_end:([0-9]+) ' ] OF '
|
|
101
|
+
array_comment:(line_comment eol ws)? non_array_data_type ws ';' ws
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
rule non_array_data_type
|
|
105
|
+
basic_data_type <BasicDataTypeNode>
|
|
106
|
+
/
|
|
107
|
+
struct_data_type <StructDataTypeNode>
|
|
108
|
+
/
|
|
109
|
+
udt_data_type <UdtDataTypeNode>
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
rule struct_data_type
|
|
113
|
+
'STRUCT' ws? eol
|
|
114
|
+
decl:declaration*
|
|
115
|
+
ws 'END_STRUCT'
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
rule root_struct_decl
|
|
119
|
+
ws struct_data_type ws ';' ws? eol
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
rule udt_data_type
|
|
123
|
+
quoted_string
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
rule identifier
|
|
127
|
+
non_ws+
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
rule line_comment
|
|
131
|
+
'//' comment_body:(!eol .)*
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
rule basic_data_type
|
|
135
|
+
(
|
|
136
|
+
'INT' /
|
|
137
|
+
'DINT' /
|
|
138
|
+
'BOOL' /
|
|
139
|
+
'BYTE' /
|
|
140
|
+
'WORD' /
|
|
141
|
+
'DWORD' /
|
|
142
|
+
'TIME_OF_DAY' /
|
|
143
|
+
'REAL' /
|
|
144
|
+
'TIME' /
|
|
145
|
+
'S5TIME' /
|
|
146
|
+
'DATE' /
|
|
147
|
+
'CHAR'
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
rule value
|
|
152
|
+
(single_quoted_string / (!(ws / ';') .)+)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
rule eol
|
|
156
|
+
"\r"? "\n"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
rule ws
|
|
160
|
+
(' ' / "\t")+
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
rule non_ws
|
|
164
|
+
(!ws .)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
rule ws_eol
|
|
168
|
+
(ws / eol)+
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'polyglot'
|
|
4
|
+
require 'treetop'
|
|
5
|
+
require 'plcutil/siemens/awl/basic_type'
|
|
6
|
+
require 'plcutil/siemens/awl/struct_type'
|
|
7
|
+
require 'plcutil/siemens/awl/array_type'
|
|
8
|
+
require 'plcutil/siemens/awl/treetop_nodes'
|
|
9
|
+
require 'plcutil/siemens/awl/db_address'
|
|
10
|
+
require 'plcutil/siemens/awl/awl.treetop'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
module PlcUtil
|
|
14
|
+
module Awl
|
|
15
|
+
class AwlFile
|
|
16
|
+
attr_reader :symlist
|
|
17
|
+
|
|
18
|
+
def initialize(filename, options = {})
|
|
19
|
+
@symlist = options[:symlist] && SymlistFile.new(options[:symlist])
|
|
20
|
+
# parse file
|
|
21
|
+
parser = PlcUtil::Awl::AwlGrammarParser.new
|
|
22
|
+
awl_nodes = parser.parse File.read(filename)
|
|
23
|
+
@awl = awl_nodes && awl_nodes.visit
|
|
24
|
+
if !@awl
|
|
25
|
+
raise [
|
|
26
|
+
"Unable to parse file: #{filename}",
|
|
27
|
+
"Failure on line #{parser.failure_line} column #{parser.failure.column}",
|
|
28
|
+
"Details:",
|
|
29
|
+
parser.failure_reason.inspect,
|
|
30
|
+
].join("\n")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def lookup_symlist(tag)
|
|
35
|
+
(options[:blocks] && options[:blocks][tag]) || (@symlist && @symlist[tag])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def each_exploded(options = {}, &block)
|
|
39
|
+
@awl[:dbs].each do |raw|
|
|
40
|
+
db = create_struct raw
|
|
41
|
+
name = if options[:no_block]
|
|
42
|
+
''
|
|
43
|
+
else
|
|
44
|
+
raw[:id]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
a = DbAddress.new 0, db_address(raw[:name])
|
|
48
|
+
db.each_exploded(a, name) do |addr, name, comment, type_name|
|
|
49
|
+
yield addr, name, comment, type_name
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def db_address(name)
|
|
55
|
+
n = @symlist[name]
|
|
56
|
+
n && n.gsub(/\s/, '')
|
|
57
|
+
end
|
|
58
|
+
private :db_address
|
|
59
|
+
|
|
60
|
+
def get_udt(name)
|
|
61
|
+
udt = @awl[:udts].find {|u| u[:name] == name }
|
|
62
|
+
raise "Could not find UDT with name: #{name}" unless udt
|
|
63
|
+
create_struct udt
|
|
64
|
+
end
|
|
65
|
+
private :get_udt
|
|
66
|
+
|
|
67
|
+
def create_struct(raw)
|
|
68
|
+
StructType.new.tap do |s|
|
|
69
|
+
raw[:entries].each do |e|
|
|
70
|
+
base_type = case e[:data_type]
|
|
71
|
+
when Hash
|
|
72
|
+
# anonymous structure inline
|
|
73
|
+
create_struct(e[:data_type])
|
|
74
|
+
when String
|
|
75
|
+
# UDT
|
|
76
|
+
get_udt e[:data_type]
|
|
77
|
+
when Symbol
|
|
78
|
+
BasicType::create e[:data_type]
|
|
79
|
+
end
|
|
80
|
+
if e.key? :array
|
|
81
|
+
s.add e[:id], ArrayType.new(base_type, e[:array]), e[:comment]
|
|
82
|
+
else
|
|
83
|
+
s.add e[:id], base_type, e[:comment]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
private :create_struct
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module PlcUtil
|
|
2
|
+
module Awl
|
|
3
|
+
class BasicType
|
|
4
|
+
TYPES = {
|
|
5
|
+
:bool => 1,
|
|
6
|
+
:byte => 8,
|
|
7
|
+
:char => 8,
|
|
8
|
+
:date => 16,
|
|
9
|
+
:dint => 32,
|
|
10
|
+
:dword => 32,
|
|
11
|
+
:int => 16,
|
|
12
|
+
:real => 32,
|
|
13
|
+
:s5time => 16,
|
|
14
|
+
:time => 32,
|
|
15
|
+
:time_of_day => 32,
|
|
16
|
+
:word => 16,
|
|
17
|
+
:date_and_time => 32, # only in VAR_TEMP
|
|
18
|
+
:timer => 1, # only in FC calls
|
|
19
|
+
:cont_c => 125 * 8
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
attr_accessor :bit_size, :type_name
|
|
23
|
+
|
|
24
|
+
def initialize(bit_size, type_name)
|
|
25
|
+
@bit_size, @type_name = bit_size, type_name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def skip_padding(addr)
|
|
29
|
+
case bit_size
|
|
30
|
+
when 1
|
|
31
|
+
addr
|
|
32
|
+
when 8
|
|
33
|
+
addr.next_byte
|
|
34
|
+
else
|
|
35
|
+
addr.next_word
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def end_address(start_addr)
|
|
40
|
+
start_addr.skip bit_size
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def BasicType.create(type_key)
|
|
44
|
+
b = TYPES[type_key]
|
|
45
|
+
raise "Could not find type: #{type_key.to_s}" unless b
|
|
46
|
+
BasicType.new b, type_key
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module PlcUtil
|
|
2
|
+
module Awl
|
|
3
|
+
class DbAddress
|
|
4
|
+
attr_accessor :data_block_addr
|
|
5
|
+
|
|
6
|
+
def initialize(bit_addr, data_block_addr = nil)
|
|
7
|
+
@bit_addr, @data_block_addr = bit_addr, data_block_addr
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_s
|
|
11
|
+
(data_block_addr || 'DB???') + ',' + byte.to_s + '.' + bit.to_s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def bit
|
|
15
|
+
@bit_addr % 8
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def byte
|
|
19
|
+
@bit_addr / 8
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def next_word
|
|
23
|
+
# simatic packing rules
|
|
24
|
+
# 1. 16 bit values aligned at modulo 2 address 0.0, 2.0, 4.0, 8.0, ...
|
|
25
|
+
# 2. 8 bit values aligned at each address, 0.0, 1.0, 2.0, ...
|
|
26
|
+
# 3. Structs are aligned modulo 2
|
|
27
|
+
# 4. Bools are aligned like 8 bit values, consecutive bools occupy a single byte
|
|
28
|
+
|
|
29
|
+
DbAddress.new(next_n(16), data_block_addr)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def next_byte
|
|
33
|
+
DbAddress.new(next_n(8), data_block_addr)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def next_n(size)
|
|
38
|
+
m = @bit_addr % size
|
|
39
|
+
if m > 0
|
|
40
|
+
(@bit_addr / size + 1) * size
|
|
41
|
+
else
|
|
42
|
+
@bit_addr
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
private :next_n
|
|
46
|
+
|
|
47
|
+
def skip(bit_count)
|
|
48
|
+
DbAddress.new @bit_addr + bit_count, data_block_addr
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module PlcUtil
|
|
2
|
+
module Awl
|
|
3
|
+
class StructType
|
|
4
|
+
def initialize
|
|
5
|
+
@children = []
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def add(name, type, comment)
|
|
9
|
+
@children << {:name => name, :type => type, :comment => comment}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def skip_padding(addr)
|
|
13
|
+
addr.next_word
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def end_address(start_addr)
|
|
17
|
+
@children.inject(start_addr) {|addr, ch| ch[:type].end_address addr }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def each_exploded(start_addr, name_prefix, &block)
|
|
21
|
+
addr = skip_padding start_addr
|
|
22
|
+
@children.each do |ch|
|
|
23
|
+
full_name = if !name_prefix || name_prefix.empty?
|
|
24
|
+
ch[:name]
|
|
25
|
+
else
|
|
26
|
+
"#{name_prefix}.#{ch[:name]}"
|
|
27
|
+
end
|
|
28
|
+
case ch[:type]
|
|
29
|
+
when StructType
|
|
30
|
+
addr = ch[:type].each_exploded(addr, full_name) do |a, n, c, t|
|
|
31
|
+
yield a, n, c, t
|
|
32
|
+
end
|
|
33
|
+
when ArrayType
|
|
34
|
+
ct = ch[:type]
|
|
35
|
+
addr = ct.skip_padding addr
|
|
36
|
+
ct.range.each do |n|
|
|
37
|
+
yield addr, "#{full_name}[#{n}]", ch[:comment], ct.element_type.type_name
|
|
38
|
+
addr = ct.element_type.end_address addr
|
|
39
|
+
end
|
|
40
|
+
when BasicType
|
|
41
|
+
ct = ch[:type]
|
|
42
|
+
addr = ct.skip_padding addr
|
|
43
|
+
yield addr, full_name, ch[:comment], ct.type_name
|
|
44
|
+
addr = ct.end_address addr
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
addr
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'dbf'
|
|
2
|
+
|
|
3
|
+
module PlcUtil
|
|
4
|
+
module Awl
|
|
5
|
+
class SymlistFile
|
|
6
|
+
def initialize(filename)
|
|
7
|
+
@symlist = {}
|
|
8
|
+
raise 'Specified symlist file not found' unless File.exists? filename
|
|
9
|
+
table = DBF::Table.new filename
|
|
10
|
+
table.each do |rec|
|
|
11
|
+
next unless rec
|
|
12
|
+
@symlist[rec.attributes['_skz']] = rec.attributes['_opiec']
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def [](tag)
|
|
17
|
+
lookup tag
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def lookup(tag)
|
|
21
|
+
@symlist[tag]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'treetop'
|
|
2
|
+
|
|
3
|
+
module PlcUtil
|
|
4
|
+
module Awl
|
|
5
|
+
module AwlGrammar
|
|
6
|
+
module StructHelpers
|
|
7
|
+
def struct_visit_helper(struct_node)
|
|
8
|
+
struct_node.decl.elements.map do |el|
|
|
9
|
+
{ :id => el.identifier.text_value }.tap do |result|
|
|
10
|
+
el.d.visit result
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module TopLevelNode
|
|
17
|
+
def visit
|
|
18
|
+
{
|
|
19
|
+
:dbs => [],
|
|
20
|
+
:udts => [],
|
|
21
|
+
:obs => []
|
|
22
|
+
}.tap do |result|
|
|
23
|
+
elements.each {|e| e.visit result }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module DbNode
|
|
29
|
+
include StructHelpers
|
|
30
|
+
def visit(root)
|
|
31
|
+
root[:dbs] << {}.tap do |db|
|
|
32
|
+
name.hash_entry db
|
|
33
|
+
db[:entries] = struct_visit_helper root_struct_decl.struct_data_type
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module UdtNode
|
|
39
|
+
include StructHelpers
|
|
40
|
+
def visit(root)
|
|
41
|
+
root[:udts] << {}.tap do |udt|
|
|
42
|
+
name.hash_entry udt
|
|
43
|
+
udt[:entries] = struct_visit_helper root_struct_decl.struct_data_type
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module ObNode
|
|
49
|
+
def visit(root)
|
|
50
|
+
root[:obs] << {}.tap do |ob|
|
|
51
|
+
ob[:title] = title.optional_title.title_quoted.v.text_value
|
|
52
|
+
name.hash_entry ob
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
module ArrayDeclarationNode
|
|
59
|
+
def visit(declaration)
|
|
60
|
+
declaration[:array] = (ar_begin.text_value.to_i)..(ar_end.text_value.to_i)
|
|
61
|
+
declaration[:comment] = array_comment.line_comment.comment_body.text_value unless array_comment.terminal?
|
|
62
|
+
non_array_data_type.visit declaration
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
module NonArrayDeclarationNode
|
|
67
|
+
def visit(declaration)
|
|
68
|
+
declaration[:comment] = comment.comment_body.text_value unless comment.terminal?
|
|
69
|
+
non_array_data_type.visit declaration
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
module BasicDataTypeNode
|
|
74
|
+
def visit(declaration)
|
|
75
|
+
declaration[:data_type] = text_value.downcase.to_sym
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
module StructDataTypeNode
|
|
80
|
+
include StructHelpers
|
|
81
|
+
def visit(declaration)
|
|
82
|
+
declaration[:data_type] = {}.tap do |struct|
|
|
83
|
+
struct[:entries] = struct_visit_helper self
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module UdtDataTypeNode
|
|
89
|
+
def visit(declaration)
|
|
90
|
+
declaration[:data_type] = v.text_value
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
module QuotedNameNode
|
|
98
|
+
def hash_entry(h)
|
|
99
|
+
h[:name] = v.text_value
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
module DbNameNode
|
|
104
|
+
def hash_entry(h)
|
|
105
|
+
h[:db_number] = number.to_s.to_i
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'clamp'
|
|
4
|
+
require 'plcutil/siemens/awl/awl_file'
|
|
5
|
+
require 'plcutil/siemens/awl/symlist_file'
|
|
6
|
+
|
|
7
|
+
module PlcUtil
|
|
8
|
+
# Command line tool to read and output an awl file
|
|
9
|
+
class AwlPrettyPrintRunner < Clamp::Command
|
|
10
|
+
def execute
|
|
11
|
+
opt = {
|
|
12
|
+
:symlist => symlist,
|
|
13
|
+
:blocks => Hash[block_name.split(/[,=]/)]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
file_list.each do |filename|
|
|
17
|
+
process_awl_file Awl::AwlFile.new(filename, opt)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def fix_name(name)
|
|
22
|
+
if no_block?
|
|
23
|
+
name.sub /^[^\.]*\./, ''
|
|
24
|
+
else
|
|
25
|
+
name
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def process_awl_file(awl)
|
|
30
|
+
awl.each_exploded do |addr, name, comment, type_name|
|
|
31
|
+
puts '%-11s %-40s%-10s%s' % [
|
|
32
|
+
addr.to_s,
|
|
33
|
+
fix_name(name),
|
|
34
|
+
type_name.to_s.upcase,
|
|
35
|
+
comment
|
|
36
|
+
]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
option %w(--no-block -n), :flag, "don't use datablock as part of the tagname"
|
|
41
|
+
option %w(--symlist -s), 'FILE', 'specify SYMLIST.DBF frfom Step 7 project'
|
|
42
|
+
option %w(--block-name -b), 'NAME=ADDR,NAME=ADDR', 'specify DB adress directly instead of using SYMLIST.DBF', :default => '' do |s|
|
|
43
|
+
raise 'Invalid format of block names, example use: A=DB20,B=DB21' unless s.match(/(\w+=\w+,)*(\w+=\w+)/)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
parameter "FILE ...", 'awl files to read (exported inside Step 7)'
|
|
47
|
+
end
|
|
48
|
+
end
|
|
File without changes
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/ruby
|
|
2
2
|
|
|
3
3
|
require 'optparse'
|
|
4
|
-
require '
|
|
5
|
-
require '
|
|
6
|
-
require '
|
|
4
|
+
require 'plcutil/siemens/awl/awl_file'
|
|
5
|
+
require 'plcutil/siemens/sdffile'
|
|
6
|
+
require 'plcutil/wonderware/intouchfile'
|
|
7
7
|
|
|
8
8
|
module PlcUtil
|
|
9
9
|
# Command line tool to read and output an awl file
|
|
File without changes
|
|
File without changes
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tallakt-plcutil
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -13,37 +13,96 @@ date: 2010-10-12 00:00:00.000000000 Z
|
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: dbf
|
|
16
|
-
requirement:
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
|
-
- -
|
|
19
|
+
- - ~>
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: 1.6
|
|
21
|
+
version: '1.6'
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements:
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ~>
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.6'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: polyglot
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ~>
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0.3'
|
|
38
|
+
type: :runtime
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ~>
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0.3'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: treetop
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ~>
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.4'
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.4'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: clamp
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ~>
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0.4'
|
|
70
|
+
type: :runtime
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ~>
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0.4'
|
|
25
78
|
description: Ruby library for using Siemens, Schneider and Intouch files
|
|
26
79
|
email: tallak@tveide.net
|
|
27
80
|
executables:
|
|
28
81
|
- awlpp
|
|
29
82
|
- intouchpp
|
|
30
83
|
- pl7tointouch
|
|
31
|
-
- s7tointouch
|
|
32
84
|
extensions: []
|
|
33
85
|
extra_rdoc_files:
|
|
34
86
|
- LICENSE
|
|
35
87
|
- README.rdoc
|
|
36
88
|
files:
|
|
37
|
-
- lib/
|
|
38
|
-
- lib/
|
|
39
|
-
- lib/
|
|
40
|
-
- lib/
|
|
41
|
-
- lib/
|
|
42
|
-
- lib/
|
|
43
|
-
- lib/
|
|
44
|
-
- lib/
|
|
45
|
-
- lib/
|
|
46
|
-
- lib/
|
|
89
|
+
- lib/plcutil/schneider/pl7_to_intouch_runner.rb
|
|
90
|
+
- lib/plcutil/siemens/awl/array_type.rb
|
|
91
|
+
- lib/plcutil/siemens/awl/awl.treetop
|
|
92
|
+
- lib/plcutil/siemens/awl/awl_file.rb
|
|
93
|
+
- lib/plcutil/siemens/awl/basic_type.rb
|
|
94
|
+
- lib/plcutil/siemens/awl/db_address.rb
|
|
95
|
+
- lib/plcutil/siemens/awl/struct_type.rb
|
|
96
|
+
- lib/plcutil/siemens/awl/symlist_file.rb
|
|
97
|
+
- lib/plcutil/siemens/awl/treetop_nodes.rb
|
|
98
|
+
- lib/plcutil/siemens/awl_pretty_print_runner.rb
|
|
99
|
+
- lib/plcutil/siemens/sdffile.rb
|
|
100
|
+
- lib/plcutil/siemens/step7_to_intouch_runner.rb
|
|
101
|
+
- lib/plcutil/version.rb
|
|
102
|
+
- lib/plcutil/wonderware/intouchfile.rb
|
|
103
|
+
- lib/plcutil/wonderware/intouch_pretty_print_runner.rb
|
|
104
|
+
- lib/plcutil/wonderware/standard_sections.yaml
|
|
105
|
+
- lib/plcutil.rb
|
|
47
106
|
- bin/awlpp
|
|
48
107
|
- bin/intouchpp
|
|
49
108
|
- bin/pl7tointouch
|
|
@@ -54,7 +113,6 @@ files:
|
|
|
54
113
|
- History.txt
|
|
55
114
|
- LICENSE
|
|
56
115
|
- README.rdoc
|
|
57
|
-
- Rakefile
|
|
58
116
|
homepage: http://github.com/tallakt/plcutil
|
|
59
117
|
licenses: []
|
|
60
118
|
post_install_message:
|
|
@@ -76,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
76
134
|
version: '0'
|
|
77
135
|
requirements: []
|
|
78
136
|
rubyforge_project:
|
|
79
|
-
rubygems_version: 1.8.
|
|
137
|
+
rubygems_version: 1.8.24
|
|
80
138
|
signing_key:
|
|
81
139
|
specification_version: 3
|
|
82
140
|
summary: Ruby PLC file library
|
data/Rakefile
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
require 'rubygems'
|
|
2
|
-
require 'rake'
|
|
3
|
-
|
|
4
|
-
require 'rake/testtask'
|
|
5
|
-
Rake::TestTask.new(:test) do |test|
|
|
6
|
-
test.libs << 'lib' << 'test'
|
|
7
|
-
test.pattern = 'test/**/test_*.rb'
|
|
8
|
-
test.verbose = true
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
begin
|
|
12
|
-
require 'rcov/rcovtask'
|
|
13
|
-
Rcov::RcovTask.new do |test|
|
|
14
|
-
test.libs << 'test'
|
|
15
|
-
test.pattern = 'test/**/test_*.rb'
|
|
16
|
-
test.verbose = true
|
|
17
|
-
end
|
|
18
|
-
rescue LoadError
|
|
19
|
-
task :rcov do
|
|
20
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
task :test => :check_dependencies
|
|
25
|
-
|
|
26
|
-
task :default => :test
|
|
27
|
-
|
|
28
|
-
require 'rake/rdoctask'
|
|
29
|
-
Rake::RDocTask.new do |rdoc|
|
|
30
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
31
|
-
|
|
32
|
-
rdoc.rdoc_dir = 'rdoc'
|
|
33
|
-
rdoc.title = "tallakt-plcutil #{version}"
|
|
34
|
-
rdoc.rdoc_files.include('README*')
|
|
35
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
36
|
-
end
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/ruby
|
|
2
|
-
|
|
3
|
-
require 'optparse'
|
|
4
|
-
require 'tallakt-plcutil/siemens/awlfile'
|
|
5
|
-
|
|
6
|
-
module PlcUtil
|
|
7
|
-
# Command line tool to read and output an awl file
|
|
8
|
-
class AwlPrettyPrintRunner
|
|
9
|
-
def initialize(args)
|
|
10
|
-
@awloptions = {}
|
|
11
|
-
@format = '%-11s %-40s%-10s%s'
|
|
12
|
-
@commentformat = '# %s / %s'
|
|
13
|
-
@output = nil
|
|
14
|
-
@symlistfile = nil
|
|
15
|
-
@no_block = false
|
|
16
|
-
|
|
17
|
-
option_parser.parse! args
|
|
18
|
-
if args.size != 1
|
|
19
|
-
show_help
|
|
20
|
-
exit
|
|
21
|
-
end
|
|
22
|
-
filename, = args
|
|
23
|
-
@awl = AwlFile.new filename, @awloptions
|
|
24
|
-
if @output
|
|
25
|
-
File.open @output, 'w' do |f|
|
|
26
|
-
print_to_file f
|
|
27
|
-
end
|
|
28
|
-
else
|
|
29
|
-
print_to_file $stdout
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def fix_name(name)
|
|
34
|
-
if @no_block
|
|
35
|
-
name.sub /^[^\.]*\./, ''
|
|
36
|
-
else
|
|
37
|
-
name
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def print_to_file(f)
|
|
42
|
-
@awl.each_tag do |tag| #|name, data_block_name, addr, comment, struct_comment, type|
|
|
43
|
-
require 'rubygems'
|
|
44
|
-
require 'pp'
|
|
45
|
-
f.puts @format % [
|
|
46
|
-
tag[:addr],
|
|
47
|
-
fix_name(tag[:name]),
|
|
48
|
-
tag[:type].to_s,
|
|
49
|
-
[tag[:comment], tag[:struct_comment]].compact! ? '' : (@commentformat % [tag[:comment], tag[:struct_comment]])
|
|
50
|
-
]
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def option_parser
|
|
55
|
-
OptionParser.new do |opts|
|
|
56
|
-
opts.banner = "Usage: awlpp [options] AWLFILE"
|
|
57
|
-
opts.on("-c", "--csv", String, "Output as CSV file") do
|
|
58
|
-
format = '%s;%s;%s;%s'
|
|
59
|
-
@commentformat = '%s'
|
|
60
|
-
end
|
|
61
|
-
opts.on("-n", "--no-block", String, "Dont use the datablock as part of the tag", "name") do
|
|
62
|
-
@no_block = true
|
|
63
|
-
end
|
|
64
|
-
opts.on("-s", "--symlist FILE", String, "Specify SYMLIST.DBF file from S7 project ") do |symlistfile|
|
|
65
|
-
@awloptions[:symlist] = symlistfile
|
|
66
|
-
end
|
|
67
|
-
opts.on("-b", "--block NAME=ADDR", String, "Define address of datablock without", "reading symlist") do |blockdef|
|
|
68
|
-
name, addr = blockdef.split(/=/)
|
|
69
|
-
@awloptions[:blocks] ||= {}
|
|
70
|
-
@awloptions[:blocks][name] = addr
|
|
71
|
-
end
|
|
72
|
-
opts.on("-o", "--output FILE", String, "Output to specified file instead of", "standard output") do |output|
|
|
73
|
-
@output = output
|
|
74
|
-
end
|
|
75
|
-
opts.on_tail("-h", "--help", "Show this message") do
|
|
76
|
-
puts opts
|
|
77
|
-
exit
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def show_help
|
|
83
|
-
puts option_parser
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
end
|
|
87
|
-
end
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/ruby
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
module PlcUtil
|
|
5
|
-
# Reads a Siemens Step 7 AWL file and creates single tags with addresses and comment
|
|
6
|
-
class AwlFile
|
|
7
|
-
attr_reader :symlist
|
|
8
|
-
|
|
9
|
-
def initialize(filename, options = {})
|
|
10
|
-
@types = {}
|
|
11
|
-
init_basic_types
|
|
12
|
-
@datablocks = []
|
|
13
|
-
@symlist = {}
|
|
14
|
-
|
|
15
|
-
if options[:symlist]
|
|
16
|
-
require 'rubygems'
|
|
17
|
-
require 'dbf' or raise 'Please install gem dbf to read symlist file'
|
|
18
|
-
|
|
19
|
-
raise 'Specified symlist file not found' unless File.exists? options[:symlist]
|
|
20
|
-
table = DBF::Table.new(options[:symlist])
|
|
21
|
-
table.each do |rec|
|
|
22
|
-
next unless 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 # array decalrations are sometimed split on two separate lines
|
|
91
|
-
in_datablock_decl = false
|
|
92
|
-
tagname = start = stop = type = comment = nil
|
|
93
|
-
db_to_fb = {} # a list connecting a DB to the FB where it was used
|
|
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
|
-
case l
|
|
104
|
-
# TODO should also cater for 'DB 90' type addresses
|
|
105
|
-
when /^TYPE "(.+?)"/
|
|
106
|
-
stack = [StructType.new($1, :datatype)]
|
|
107
|
-
add_type stack.first
|
|
108
|
-
when /^FUNCTION_BLOCK "(.+?)"/
|
|
109
|
-
stack = [StructType.new($1, :functionblock)]
|
|
110
|
-
add_type stack.first
|
|
111
|
-
when /^DATA_BLOCK ("(.+?)"|DB\s+(\d+))/
|
|
112
|
-
in_datablock_decl = true
|
|
113
|
-
name = $2 || ('DB' + $3)
|
|
114
|
-
stack = [StructType.new(name, :datablock)]
|
|
115
|
-
@datablocks << Variable.new(name, stack.last)
|
|
116
|
-
when /^VAR_TEMP/
|
|
117
|
-
s = StructType.new('VAR_TEMP', :anonymous)
|
|
118
|
-
stack = [s]
|
|
119
|
-
when /^\s*(\S+) : STRUCT /
|
|
120
|
-
in_datablock_decl = false
|
|
121
|
-
s = StructType.new('STRUCT', :anonymous)
|
|
122
|
-
stack.last.add Variable.new($1, s)
|
|
123
|
-
stack << stack.last.children.last.type
|
|
124
|
-
when /^BEGIN$/
|
|
125
|
-
in_datablock_decl = false
|
|
126
|
-
when /^\s+BEGIN/
|
|
127
|
-
stack.pop
|
|
128
|
-
# New variable in struct or data block
|
|
129
|
-
when /^\s+([A-Za-z0-9_ ]+) : "?([A-Za-z0-9_ ]+?)"?\s*(:=\s*[^;]+)?;(\s*\/\/(.*))?/
|
|
130
|
-
if stack.any?
|
|
131
|
-
tagname, type_name, comment = $1, $2, ($5 || '')
|
|
132
|
-
stack.last.add Variable.new(tagname, lookup_type(type_name), comment)
|
|
133
|
-
end
|
|
134
|
-
when /^\s+([A-Za-z0-9_]+) : ARRAY\s*\[(\d+)\D+(\d+) \] OF "?([A-Za-z0-9_]+)"?\s?;(\s*\/\/(.*))?/
|
|
135
|
-
tagname, start, stop, type, comment = $1, $2, $3, $4, $6
|
|
136
|
-
stack.last.add Variable.new(tagname, ArrayType.new(lookup_type(type), start..stop), comment)
|
|
137
|
-
when /^\s+([A-Za-z0-9_]+) : ARRAY\s*\[(\d+)\D+(\d+) \] OF(\s?\/\/(.*))?$/
|
|
138
|
-
tagname, start, stop, comment = $1, $2, $3, $4
|
|
139
|
-
in_array_decl = true
|
|
140
|
-
when /^"(.*?)"$/
|
|
141
|
-
# datablock definition is stored in FB definition
|
|
142
|
-
# remember which FB to use but assign the FB
|
|
143
|
-
# only after all blocks have been loaded
|
|
144
|
-
db_to_fb[stack.first.name] = $1 if in_datablock_decl
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Update FB to DB use
|
|
150
|
-
@datablocks.each do |db|
|
|
151
|
-
if db_to_fb.key? db.name
|
|
152
|
-
fb = lookup_type db_to_fb[db.name]
|
|
153
|
-
fb.children.each {|child| db.type.add child } if fb.respond_to? :children
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def defined_type(type)
|
|
159
|
-
@types.key?(type.name)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class BasicType
|
|
164
|
-
attr_accessor :bit_size, :name
|
|
165
|
-
|
|
166
|
-
def initialize(name, bit_size)
|
|
167
|
-
@bit_size, @name = bit_size, name
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def explode(start_addr, name, comment, struct_comment, options = {})
|
|
171
|
-
actual_start = start_addr.next_start bit_size
|
|
172
|
-
[{:addr => actual_start, :name => name,
|
|
173
|
-
:struct_comment => struct_comment, :comment => comment, :type => @name}]
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def end_address(start_address)
|
|
177
|
-
start_address.next_start(bit_size).skip! bit_size
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class StructType
|
|
184
|
-
attr_accessor :name, :children, :type
|
|
185
|
-
|
|
186
|
-
def initialize(name = 'STRUCT', type = :anonymous)
|
|
187
|
-
@name = name
|
|
188
|
-
@children = []
|
|
189
|
-
@type = type
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def add(child)
|
|
193
|
-
raise 'Added nil child' unless child
|
|
194
|
-
raise 'Added nil child type' unless child.type
|
|
195
|
-
@children << child
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def end_address(start_address)
|
|
199
|
-
addr = start_address.next_start
|
|
200
|
-
@children.each do |child|
|
|
201
|
-
addr = child.type.end_address addr
|
|
202
|
-
end
|
|
203
|
-
addr.next_start!
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def explode(start_addr, name, comment = nil, struct_comment = nil, options = {})
|
|
207
|
-
addr = start_addr.next_start
|
|
208
|
-
exploded = []
|
|
209
|
-
@children.each do |child|
|
|
210
|
-
|
|
211
|
-
exploded += child.type.explode(addr, [name, child.name].compact.join('.'), child.comment,
|
|
212
|
-
type == :datablock ? child.comment : struct_comment, options)
|
|
213
|
-
addr = child.type.end_address addr
|
|
214
|
-
end
|
|
215
|
-
exploded
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
class ArrayType
|
|
220
|
-
attr_accessor :range, :type
|
|
221
|
-
|
|
222
|
-
def initialize(type, range)
|
|
223
|
-
raise 'Added nil array type' unless type
|
|
224
|
-
raise 'Added nil array range' unless range
|
|
225
|
-
@range, @type = range, type
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def name
|
|
229
|
-
'ARRAY'
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def end_address(start_address)
|
|
233
|
-
addr = start_address.next_start
|
|
234
|
-
range.each do
|
|
235
|
-
addr = type.end_address addr
|
|
236
|
-
end
|
|
237
|
-
addr
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
def explode(start_addr, name, comment, struct_comment, options = {})
|
|
241
|
-
exploded = []
|
|
242
|
-
addr = start_addr.next_start
|
|
243
|
-
range.to_a.each_with_index do |v, i|
|
|
244
|
-
exploded += type.explode(addr, name + '[' + v.to_s + ']', comment, struct_comment, options = {})
|
|
245
|
-
addr = type.end_address(addr)
|
|
246
|
-
end
|
|
247
|
-
exploded
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
class Variable
|
|
252
|
-
attr_accessor :name, :type, :comment
|
|
253
|
-
|
|
254
|
-
def initialize(name, type, comment = nil)
|
|
255
|
-
@name, @type, @comment= name, type, comment
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
class Address
|
|
260
|
-
attr_accessor :bit_addr, :data_block_addr
|
|
261
|
-
|
|
262
|
-
def initialize(bit_addr, data_block_addr = nil)
|
|
263
|
-
@bit_addr, @data_block_addr = bit_addr, data_block_addr
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def to_s
|
|
267
|
-
(data_block_addr || 'DB???') + ',' + byte.to_s + '.' + bit.to_s
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def bit
|
|
271
|
-
@bit_addr % 8
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
def byte
|
|
275
|
-
@bit_addr / 8
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def next_start!(bit_size = 2 * 8)
|
|
279
|
-
bs = [bit_size, 2 * 8].min # bools use bits, chars/bytes one byte others two byte rounding of start address
|
|
280
|
-
rest = @bit_addr % bs
|
|
281
|
-
@bit_addr += (bs - rest) if rest > 0
|
|
282
|
-
self
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
def next_start(bit_size = 2 * 8)
|
|
286
|
-
self.clone.next_start! bit_size
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def skip!(bit_count)
|
|
290
|
-
@bit_addr += bit_count
|
|
291
|
-
self
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
end
|