1
1
import os
2
2
import shutil
3
3
import struct
4
- from dataclasses import dataclass
4
+ from dataclasses import dataclass , field
5
+ from enum import Enum
5
6
from os import PathLike
6
7
from pathlib import Path
7
8
from sqlite3 import Cursor
@@ -38,7 +39,7 @@ def to_xml(self):
38
39
if isinstance (attribute_value , L5xElement ):
39
40
child_list .append (attribute_value .to_xml ())
40
41
elif isinstance (attribute_value , list ):
41
- if attribute == "tags" :
42
+ if attribute == "tags" or attribute == "data_types" or attribute == "members" :
42
43
new_child_list : List [str ] = []
43
44
for element in attribute_value :
44
45
if isinstance (element , L5xElement ):
@@ -48,16 +49,30 @@ def to_xml(self):
48
49
child_list .append (f'<{ attribute .title ().replace ("_" , "" )} >{ " " .join (new_child_list )} </{ attribute .title ().replace ("_" , "" )} >' )
49
50
50
51
else :
52
+ if attribute == "cls" :
53
+ attribute = "class"
51
54
attribute_list .append (f'{ attribute .title ().replace ("_" , "" )} ="{ attribute_value } "' )
52
55
53
56
_export_name = self .__class__ .__name__ .title ().replace ("_" , "" )
54
57
return f'<{ _export_name } { " " .join (attribute_list )} >{ " " .join (child_list )} </{ _export_name } >'
55
58
56
59
60
+ @dataclass
61
+ class Member (L5xElement ):
62
+ name : str
63
+ data_type : str
64
+ dimension : int
65
+ radix : str
66
+ hidden : bool
67
+ external_access : str
68
+
69
+
57
70
@dataclass
58
71
class DataType (L5xElement ):
59
72
name : str
60
- children : List [str ]
73
+ family : str
74
+ cls : str
75
+ members : List [Member ]
61
76
62
77
63
78
@dataclass
@@ -131,6 +146,85 @@ def __post_init__(self):
131
146
self ._name = "RSLogix5000Content"
132
147
133
148
149
+ def radix_enum (i : int ) -> str :
150
+ if i == 0 :
151
+ return "NullType"
152
+ if i == 1 :
153
+ return "General"
154
+ if i == 2 :
155
+ return "Binary"
156
+ if i == 3 :
157
+ return "Octal"
158
+ if i == 4 :
159
+ return "Decimal"
160
+ if i == 5 :
161
+ return "Hex"
162
+ if i == 6 :
163
+ return "Exponential"
164
+ if i == 7 :
165
+ return "Float"
166
+ if i == 8 :
167
+ return "ASCII"
168
+ if i == 9 :
169
+ return "Unicode"
170
+ if i == 10 :
171
+ return "Date/Time"
172
+ if i == 11 :
173
+ return "Date/Time (ns)"
174
+ if i == 12 :
175
+ return "UseTypeStyle"
176
+ return "General"
177
+
178
+
179
+ def external_access_enum (i : int ) -> str :
180
+ if i == 0 :
181
+ return "Read/Write"
182
+ if i == 1 :
183
+ return "Read Only"
184
+ if i == 2 :
185
+ return "None"
186
+ return "Read/Write"
187
+
188
+ @dataclass
189
+ class MemberBuilder (L5xElementBuilder ):
190
+ record : List [int ] = field (default_factory = [])
191
+
192
+ def build (self ) -> Member :
193
+ self ._cur .execute (
194
+ "SELECT comp_name, object_id, parent_id, record FROM comps WHERE object_id=" + str (
195
+ self ._object_id ))
196
+ results = self ._cur .fetchall ()
197
+
198
+ name = results [0 ][0 ]
199
+ r = RxGeneric .from_bytes (results [0 ][3 ])
200
+ try :
201
+ r = RxGeneric .from_bytes (results [0 ][3 ])
202
+ except Exception as e :
203
+ return Member (name , name , "" , 0 , "Decimal" , False , "Read/Write" )
204
+
205
+ extended_records : Dict [int , List [int ]] = {}
206
+ for extended_record in r .extended_records :
207
+ extended_records [extended_record .attribute_id ] = extended_record .value
208
+ extended_records [r .last_extended_record .attribute_id ] = r .last_extended_record .value
209
+
210
+ cip_data_typoe = struct .unpack_from ("<I" , self .record , 0x78 )[0 ]
211
+ dimension = struct .unpack_from ("<I" , self .record , 0x5C )[0 ]
212
+ radix = radix_enum (struct .unpack_from ("<I" , self .record , 0x54 )[0 ])
213
+ data_type_id = struct .unpack_from ("<I" , self .record , 0x58 )[0 ]
214
+ hidden = bool (struct .unpack_from ("<I" , self .record , 0x70 )[0 ])
215
+ external_access = external_access_enum (struct .unpack_from ("<I" , self .record , 0x74 )[0 ])
216
+
217
+
218
+ self ._cur .execute (
219
+ "SELECT comp_name, object_id, parent_id, record FROM comps WHERE object_id=" + str (
220
+ data_type_id ))
221
+ data_type_results = self ._cur .fetchall ()
222
+ data_type = data_type_results [0 ][0 ]
223
+
224
+
225
+ return Member (name , name , data_type , dimension , radix , hidden , external_access )
226
+
227
+
134
228
@dataclass
135
229
class DataTypeBuilder (L5xElementBuilder ):
136
230
@@ -140,25 +234,53 @@ def build(self) -> DataType:
140
234
self ._object_id ))
141
235
results = self ._cur .fetchall ()
142
236
143
- record = results [0 ][3 ]
144
237
name = results [0 ][0 ]
145
238
239
+ try :
240
+ r = RxGeneric .from_bytes (results [0 ][3 ])
241
+ except Exception as e :
242
+ return DataType (name , name , "NoFamily" , "User" , [])
243
+
244
+ extended_records : Dict [int , List [int ]] = {}
245
+ for extended_record in r .extended_records :
246
+ extended_records [extended_record .attribute_id ] = extended_record .value
247
+ extended_records [r .last_extended_record .attribute_id ] = r .last_extended_record .value
248
+
249
+ string_family_int = struct .unpack ("<I" , extended_records [0x6C ])[0 ]
250
+ string_family = "StringFamily" if string_family_int == 1 else "NoFamily"
251
+
252
+ built_in = struct .unpack ("<I" , extended_records [0x67 ])[0 ]
253
+ module_defined = struct .unpack ("<I" , extended_records [0x69 ])[0 ]
254
+
255
+ class_type = "User"
256
+ if module_defined > 0 :
257
+ class_type = "IO"
258
+ if built_in > 0 :
259
+ class_type = "ProductDefined"
260
+ if len (extended_records [0x64 ]) == 0x04 :
261
+ member_count = struct .unpack ("<I" , extended_records [0x64 ])[0 ]
262
+ else :
263
+ member_count = 0
264
+
146
265
self ._cur .execute (
147
266
"SELECT comp_name, object_id, parent_id, record FROM comps WHERE parent_id=" + str (
148
267
self ._object_id ))
149
268
member_results = self ._cur .fetchall ()
269
+ children : List [Member ] = []
150
270
if len (member_results ) == 1 :
151
271
member_collection_id = member_results [0 ][1 ]
152
272
153
273
self ._cur .execute (
154
- "SELECT comp_name, object_id, parent_id, record FROM comps WHERE parent_id=" + str (
155
- member_collection_id ))
274
+ f"SELECT comp_name, object_id, parent_id, seq_number, record FROM comps WHERE parent_id={ member_collection_id } ORDER BY seq_number" )
156
275
children_results = self ._cur .fetchall ()
157
- children = []
158
- for child in children_results :
159
- children .append (child [0 ])
160
- return DataType (name , name , children )
161
- return DataType (name , name , [])
276
+
277
+ if member_count != len (children_results ):
278
+ raise Exception ("Member and children list arent the same length" )
279
+
280
+ for idx , child in enumerate (children_results ):
281
+ children .append (MemberBuilder (self ._cur , child [1 ], extended_records [0x6E + idx ]).build ())
282
+
283
+ return DataType (name , name , string_family , class_type , children )
162
284
163
285
164
286
@dataclass
@@ -236,13 +358,16 @@ def build(self) -> Tag:
236
358
name_length = struct .unpack ("<H" , extended_records [0x01 ][0 :2 ])[0 ]
237
359
name = bytes (extended_records [0x01 ][2 :name_length + 2 ]).decode ('utf-8' )
238
360
361
+ radix = radix_enum (r .main_record .radix )
362
+ external_access = external_access_enum (r .main_record .external_access )
363
+
239
364
if r .main_record .dimension_1 != 0 :
240
365
data_type = data_type + "[" + str (r .main_record .dimension_1 ) + "]"
241
366
if r .main_record .dimension_2 != 0 :
242
367
data_type = data_type + "[" + str (r .main_record .dimension_2 ) + "]"
243
368
if r .main_record .dimension_3 != 0 :
244
369
data_type = data_type + "[" + str (r .main_record .dimension_3 ) + "]"
245
- return Tag (name , name , "Base" , data_type , "Decimal" , "Read/Write" , r .main_record .data_table_instance , comment_results )
370
+ return Tag (name , name , "Base" , data_type , radix , external_access , r .main_record .data_table_instance , comment_results )
246
371
247
372
248
373
@dataclass
0 commit comments