|
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 #This file is part of Beremiz, an Integrated Development Environment for |
|
5 #programming IEC 61131-3 automates supporting EtherCAT. |
|
6 # |
|
7 #Copyright (C) 2013: Real-Time & Embedded Systems (RTES) Lab. University of Seoul, Korea |
|
8 # |
|
9 #See COPYING file for copyrights details. |
|
10 # |
|
11 #This library is free software; you can redistribute it and/or |
|
12 #modify it under the terms of the GNU General Public |
|
13 #License as published by the Free Software Foundation; either |
|
14 #version 2.1 of the License, or (at your option) any later version. |
|
15 # |
|
16 #This library is distributed in the hope that it will be useful, |
|
17 #but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
19 #General Public License for more details. |
|
20 # |
|
21 #You should have received a copy of the GNU General Public |
|
22 #License along with this library; if not, write to the Free Software |
|
23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
24 |
|
25 |
|
26 import os |
|
27 import wx |
|
28 |
|
29 def ExtractHexDecValue(value): |
|
30 """ |
|
31 convert numerical value in string format into decimal or hex format. |
|
32 @param value : hex or decimal data |
|
33 @return integer data |
|
34 """ |
|
35 try: |
|
36 return int(value) |
|
37 except: |
|
38 pass |
|
39 try: |
|
40 return int(value.replace("#", "0"), 16) |
|
41 |
|
42 except: |
|
43 raise ValueError, "Invalid value for HexDecValue \"%s\"" % value |
|
44 |
|
45 def ExtractName(names, default=None): |
|
46 """ |
|
47 Extract "name" field from XML entries. |
|
48 @param names : XML entry |
|
49 @default : if it fails to extract from the designated XML entry, return the default value ("None"). |
|
50 @return default or the name extracted |
|
51 """ |
|
52 if len(names) == 1: |
|
53 return names[0].getcontent() |
|
54 else: |
|
55 for name in names: |
|
56 if name.getLcId() == 1033: |
|
57 return name.getcontent() |
|
58 return default |
|
59 |
|
60 #-------------------------------------------------- |
|
61 # Remote Exec Etherlab Commands |
|
62 #-------------------------------------------------- |
|
63 |
|
64 # --------------------- for master --------------------------- |
|
65 MASTER_STATE = """ |
|
66 import commands |
|
67 result = commands.getoutput("ethercat master") |
|
68 returnVal =result |
|
69 """ |
|
70 |
|
71 # --------------------- for slave ---------------------------- |
|
72 # ethercat state -p (slave position) (state (INIT, PREOP, SAFEOP, OP)) |
|
73 SLAVE_STATE = """ |
|
74 import commands |
|
75 result = commands.getoutput("ethercat state -p %d %s") |
|
76 returnVal = result |
|
77 """ |
|
78 |
|
79 # ethercat slave |
|
80 GET_SLAVE = """ |
|
81 import commands |
|
82 result = commands.getoutput("ethercat slaves") |
|
83 returnVal =result |
|
84 """ |
|
85 |
|
86 # ethercat xml -p (slave position) |
|
87 SLAVE_XML = """ |
|
88 import commands |
|
89 result = commands.getoutput("ethercat xml -p %d") |
|
90 returnVal = result |
|
91 """ |
|
92 |
|
93 # ethercat sdos -p (slave position) |
|
94 SLAVE_SDO = """ |
|
95 import commands |
|
96 result = commands.getoutput("ethercat sdos -p %d") |
|
97 returnVal =result |
|
98 """ |
|
99 |
|
100 # ethercat upload -p (slave position) (main index) (sub index) |
|
101 GET_SLOW_SDO = """ |
|
102 import commands |
|
103 result = commands.getoutput("ethercat upload -p %d %s %s") |
|
104 returnVal =result |
|
105 """ |
|
106 |
|
107 # ethercat download -p (slave position) (main index) (sub index) (value) |
|
108 SDO_DOWNLOAD = """ |
|
109 import commands |
|
110 result = commands.getoutput("ethercat download --type %s -p %d %s %s %s") |
|
111 returnVal =result |
|
112 """ |
|
113 |
|
114 # ethercat sii_read -p (slave position) |
|
115 SII_READ = """ |
|
116 import commands |
|
117 result = commands.getoutput("ethercat sii_read -p %d") |
|
118 returnVal =result |
|
119 """ |
|
120 |
|
121 # ethercat reg_read -p (slave position) (address) (size) |
|
122 REG_READ = """ |
|
123 import commands |
|
124 result = commands.getoutput("ethercat reg_read -p %d %s %s") |
|
125 returnVal =result |
|
126 """ |
|
127 |
|
128 # ethercat sii_write -p (slave position) - (contents) |
|
129 SII_WRITE = """ |
|
130 import subprocess |
|
131 process = subprocess.Popen( |
|
132 ["ethercat", "-f", "sii_write", "-p", "%d", "-"], |
|
133 stdin=subprocess.PIPE) |
|
134 process.communicate(sii_data) |
|
135 returnVal = process.returncode |
|
136 """ |
|
137 |
|
138 # ethercat reg_write -p (slave position) -t (uinit16) (address) (data) |
|
139 REG_WRITE = """ |
|
140 import commands |
|
141 result = commands.getoutput("ethercat reg_write -p %d -t uint16 %s %s") |
|
142 returnVal =result |
|
143 """ |
|
144 |
|
145 # ethercat rescan -p (slave position) |
|
146 RESCAN = """ |
|
147 import commands |
|
148 result = commands.getoutput("ethercat rescan -p %d") |
|
149 returnVal =result |
|
150 """ |
|
151 |
|
152 #-------------------------------------------------- |
|
153 # Common Method For EtherCAT Management |
|
154 #-------------------------------------------------- |
|
155 class _CommonSlave: |
|
156 |
|
157 # ----- Data Structure for ethercat management ---- |
|
158 SlaveState = "" |
|
159 |
|
160 # category of SDO data |
|
161 DatatypeDescription, CommunicationObject, ManufacturerSpecific, \ |
|
162 ProfileSpecific, Reserved, AllSDOData = range(6) |
|
163 |
|
164 # store the execution result of "ethercat sdos" command into SaveSDOData. |
|
165 SaveSDOData = [] |
|
166 |
|
167 # Flags for checking "write" permission of OD entries |
|
168 CheckPREOP = False |
|
169 CheckSAFEOP = False |
|
170 CheckOP = False |
|
171 |
|
172 # Save PDO Data |
|
173 TxPDOInfo = [] |
|
174 TxPDOCategory = [] |
|
175 RxPDOInfo = [] |
|
176 RxPDOCategory = [] |
|
177 |
|
178 # Save EEPROM Data |
|
179 SiiData = "" |
|
180 |
|
181 # Save Register Data |
|
182 RegData = "" |
|
183 CrtRegSpec = {"ESCType": "", |
|
184 "FMMUNumber": "", |
|
185 "SMNumber": "", |
|
186 "PDIType": ""} |
|
187 |
|
188 def __init__(self, controler): |
|
189 """ |
|
190 Constructor |
|
191 @param controler: _EthercatSlaveCTN class in EthercatSlave.py |
|
192 """ |
|
193 self.Controler = controler |
|
194 |
|
195 self.ClearSDODataSet() |
|
196 |
|
197 #------------------------------------------------------------------------------- |
|
198 # Used Master State |
|
199 #------------------------------------------------------------------------------- |
|
200 def GetMasterState(self): |
|
201 """ |
|
202 Execute "ethercat master" command and parse the execution result |
|
203 @return MasterState |
|
204 """ |
|
205 |
|
206 # exectute "ethercat master" command |
|
207 error, return_val = self.Controler.RemoteExec(MASTER_STATE, return_val = None) |
|
208 master_state = {} |
|
209 # parse the reslut |
|
210 for each_line in return_val.splitlines(): |
|
211 if len(each_line) > 0 : |
|
212 chunks = each_line.strip().split(':', 1) |
|
213 key = chunks[0] |
|
214 value = [] |
|
215 if len(chunks) > 1 : |
|
216 value = chunks[1].split() |
|
217 if '(attached)' in value: |
|
218 value.remove('(attached)') |
|
219 master_state[key] = value |
|
220 |
|
221 return master_state |
|
222 |
|
223 #------------------------------------------------------------------------------- |
|
224 # Used Slave State |
|
225 #------------------------------------------------------------------------------- |
|
226 def RequestSlaveState(self, command): |
|
227 """ |
|
228 Set slave state to the specified one using "ethercat states -p %d %s" command. |
|
229 Command example : "ethercat states -p 0 PREOP" (target slave position and target state are given.) |
|
230 @param command : target slave state |
|
231 """ |
|
232 error, return_val = self.Controler.RemoteExec(SLAVE_STATE%(self.Controler.GetSlavePos(), command), return_val = None) |
|
233 |
|
234 def GetSlaveStateFromSlave(self): |
|
235 """ |
|
236 Get slave information using "ethercat slaves" command and store the information into internal data structure |
|
237 (self.SlaveState) for "Slave State" |
|
238 return_val example : 0 0:0 PREOP + EL9800 (V4.30) (PIC24, SPI, ET1100) |
|
239 """ |
|
240 error, return_val = self.Controler.RemoteExec(GET_SLAVE, return_val = None) |
|
241 self.SlaveState = return_val |
|
242 return return_val |
|
243 |
|
244 #------------------------------------------------------------------------------- |
|
245 # Used SDO Management |
|
246 #------------------------------------------------------------------------------- |
|
247 def GetSlaveSDOFromSlave(self): |
|
248 """ |
|
249 Get SDO objects information of current slave using "ethercat sdos -p %d" command. |
|
250 Command example : "ethercat sdos -p 0" |
|
251 @return return_val : execution results of "ethercat sdos" command (need to be parsed later) |
|
252 """ |
|
253 error, return_val = self.Controler.RemoteExec(SLAVE_SDO%(self.Controler.GetSlavePos()), return_val = None) |
|
254 return return_val |
|
255 |
|
256 def SDODownload(self, data_type, idx, sub_idx, value): |
|
257 """ |
|
258 Set an SDO object value to user-specified value using "ethercat download" command. |
|
259 Command example : "ethercat download --type int32 -p 0 0x8020 0x12 0x00000000" |
|
260 @param data_type : data type of SDO entry |
|
261 @param idx : index of the SDO entry |
|
262 @param sub_idx : subindex of the SDO entry |
|
263 @param value : value of SDO entry |
|
264 """ |
|
265 error, return_val = self.Controler.RemoteExec(SDO_DOWNLOAD%(data_type, self.Controler.GetSlavePos(), idx, sub_idx, value), return_val = None) |
|
266 |
|
267 def BackupSDODataSet(self): |
|
268 """ |
|
269 Back-up current SDO entry information to restore the SDO data |
|
270 in case that the user cancels SDO update operation. |
|
271 """ |
|
272 self.BackupDatatypeDescription = self.SaveDatatypeDescription |
|
273 self.BackupCommunicationObject = self.SaveCommunicationObject |
|
274 self.BackupManufacturerSpecific = self.SaveManufacturerSpecific |
|
275 self.BackupProfileSpecific = self.SaveProfileSpecific |
|
276 self.BackupReserved = self.SaveReserved |
|
277 self.BackupAllSDOData = self.SaveAllSDOData |
|
278 |
|
279 def ClearSDODataSet(self): |
|
280 """ |
|
281 Clear the specified SDO entry information. |
|
282 """ |
|
283 for count in range(6): |
|
284 self.SaveSDOData.append([]) |
|
285 |
|
286 #------------------------------------------------------------------------------- |
|
287 # Used PDO Monitoring |
|
288 #------------------------------------------------------------------------------- |
|
289 def RequestPDOInfo(self): |
|
290 """ |
|
291 Load slave information from RootClass (XML data) and parse the information (calling SlavePDOData() method). |
|
292 """ |
|
293 # Load slave information from ESI XML file (def EthercatMaster.py) |
|
294 slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos()) |
|
295 |
|
296 type_infos = slave.getType() |
|
297 device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos) |
|
298 # Initialize PDO data set |
|
299 self.ClearDataSet() |
|
300 |
|
301 # if 'device' object is valid, call SavePDOData() to parse PDO data |
|
302 if device is not None : |
|
303 self.SavePDOData(device) |
|
304 |
|
305 def SavePDOData(self, device): |
|
306 """ |
|
307 Parse PDO data and store the results in TXPDOCategory and RXPDOCategory |
|
308 Tx(Rx)PDOCategory : index, name, entry number |
|
309 Tx(Rx)Info : entry index, sub index, name, length, type |
|
310 @param device : Slave information extracted from ESI XML file |
|
311 """ |
|
312 # Parsing TXPDO entries |
|
313 for pdo, pdo_info in ([(pdo, "Inputs") for pdo in device.getTxPdo()]): |
|
314 # Save pdo_index, entry, and name of each entry |
|
315 pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent()) |
|
316 entries = pdo.getEntry() |
|
317 pdo_name = ExtractName(pdo.getName()) |
|
318 |
|
319 # Initialize entry number count |
|
320 count = 0 |
|
321 |
|
322 # Parse entries |
|
323 for entry in entries: |
|
324 # Save index and subindex |
|
325 index = ExtractHexDecValue(entry.getIndex().getcontent()) |
|
326 subindex = ExtractHexDecValue(entry.getSubIndex()) |
|
327 # if entry name exists, save entry data |
|
328 if ExtractName(entry.getName()) is not None : |
|
329 entry_infos = { |
|
330 "entry_index" : index, |
|
331 "subindex" : subindex, |
|
332 "name" : ExtractName(entry.getName()), |
|
333 "bitlen" : entry.getBitLen(), |
|
334 "type" : entry.getDataType().getcontent() |
|
335 } |
|
336 self.TxPDOInfo.append(entry_infos) |
|
337 count += 1 |
|
338 |
|
339 categorys = {"pdo_index" : pdo_index, "name" : pdo_name, "number_of_entry" : count} |
|
340 self.TxPDOCategory.append(categorys) |
|
341 |
|
342 # Parsing RxPDO entries |
|
343 for pdo, pdo_info in ([(pdo, "Outputs") for pdo in device.getRxPdo()]): |
|
344 # Save pdo_index, entry, and name of each entry |
|
345 pdo_index = ExtractHexDecValue(pdo.getIndex().getcontent()) |
|
346 entries = pdo.getEntry() |
|
347 pdo_name = ExtractName(pdo.getName()) |
|
348 |
|
349 # Initialize entry number count |
|
350 count = 0 |
|
351 |
|
352 # Parse entries |
|
353 for entry in entries: |
|
354 # Save index and subindex |
|
355 index = ExtractHexDecValue(entry.getIndex().getcontent()) |
|
356 subindex = ExtractHexDecValue(entry.getSubIndex()) |
|
357 # if entry name exists, save entry data |
|
358 if ExtractName(entry.getName()) is not None : |
|
359 entry_infos = { |
|
360 "entry_index" : index, |
|
361 "subindex" : subindex, |
|
362 "name" : ExtractName(entry.getName()), |
|
363 "bitlen" : str(entry.getBitLen()), |
|
364 "type" : entry.getDataType().getcontent() |
|
365 } |
|
366 self.RxPDOInfo.append(entry_infos) |
|
367 count += 1 |
|
368 |
|
369 categorys = {"pdo_index" : pdo_index, "name" : pdo_name, "number_of_entry" : count} |
|
370 self.RxPDOCategory.append(categorys) |
|
371 |
|
372 def GetTxPDOCategory(self): |
|
373 """ |
|
374 Get TxPDOCategory data structure (Meta informaton of TxPDO). |
|
375 TxPDOCategorys : index, name, number of entries |
|
376 @return TxPDOCategorys |
|
377 """ |
|
378 return self.TxPDOCategory |
|
379 |
|
380 def GetRxPDOCategory(self): |
|
381 """ |
|
382 Get RxPDOCategory data structure (Meta information of RxPDO). |
|
383 RxPDOCategorys : index, name, number of entries |
|
384 @return RxPDOCategorys |
|
385 """ |
|
386 return self.RxPDOCategory |
|
387 |
|
388 def GetTxPDOInfo(self): |
|
389 """ |
|
390 Get TxPDOInfo data structure (Detailed information on TxPDO entries). |
|
391 TxPDOInfos : entry index, sub index, name, length, type |
|
392 @return TxPDOInfos |
|
393 """ |
|
394 return self.TxPDOInfo |
|
395 |
|
396 def GetRxPDOInfo(self): |
|
397 """ |
|
398 Get RxPDOInfo data structure (Detailed information on RxPDO entries). |
|
399 RxPDOInfos : entry index, sub index, name, length, type |
|
400 @return RxPDOInfos |
|
401 """ |
|
402 return self.RxPDOInfo |
|
403 |
|
404 def ClearDataSet(self): |
|
405 """ |
|
406 Initialize PDO management data structure. |
|
407 """ |
|
408 self.TxPDOInfos = [] |
|
409 self.TxPDOCategorys = [] |
|
410 self.RxPDOInfos = [] |
|
411 self.RxPDOCategorys = [] |
|
412 |
|
413 #------------------------------------------------------------------------------- |
|
414 # Used EEPROM Management |
|
415 #------------------------------------------------------------------------------- |
|
416 # Base data types in ETG2000; format = {"Name": "BitSize"} |
|
417 BaseDataTypeDict = {"BOOL": "01", |
|
418 "SINT": "02", |
|
419 "INT": "03", |
|
420 "DINT": "04", |
|
421 "USINT": "05", |
|
422 "UINT": "06", |
|
423 "UDINT": "07", |
|
424 "REAL": "08", |
|
425 "INT24": "10", |
|
426 "LREAL": "11", |
|
427 "INT40": "12", |
|
428 "INT48": "13", |
|
429 "INT56": "14", |
|
430 "LINT": "15", |
|
431 "UINT24": "16", |
|
432 "UINT40": "18", |
|
433 "UINT48": "19", |
|
434 "UINT56": "1a", |
|
435 "ULINT": "1b", |
|
436 "USINT": "1e", |
|
437 "BITARR8": "2d", |
|
438 "BITARR16": "2e", |
|
439 "BITARR32": "2f", |
|
440 "BIT1": "30", |
|
441 "BIT2": "31", |
|
442 "BIT3": "32", |
|
443 "BIT4": "33", |
|
444 "BIT5": "34", |
|
445 "BIT6": "35", |
|
446 "BIT7": "36", |
|
447 "BIT8": "37"} |
|
448 |
|
449 def GetSmartViewInfos(self): |
|
450 """ |
|
451 Parse XML data for "Smart View" of EEPROM contents. |
|
452 @return smartview_infos : EEPROM contents dictionary |
|
453 """ |
|
454 |
|
455 smartview_infos = {"eeprom_size": 128, |
|
456 "pdi_type": 0, |
|
457 "device_emulation": "False", |
|
458 "vendor_id": '0x00000000', |
|
459 "product_code": '0x00000000', |
|
460 "revision_no": '0x00000000', |
|
461 "serial_no": '0x00000000', |
|
462 "supported_mailbox": "", |
|
463 "mailbox_bootstrapconf_outstart": '0', |
|
464 "mailbox_bootstrapconf_outlength": '0', |
|
465 "mailbox_bootstrapconf_instart": '0', |
|
466 "mailbox_bootstrapconf_inlength": '0', |
|
467 "mailbox_standardconf_outstart": '0', |
|
468 "mailbox_standardconf_outlength": '0', |
|
469 "mailbox_standardconf_instart": '0', |
|
470 "mailbox_standardconf_inlength": '0'} |
|
471 |
|
472 slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos()) |
|
473 type_infos = slave.getType() |
|
474 device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos) |
|
475 |
|
476 # 'device' represents current slave device selected by user |
|
477 if device is not None: |
|
478 for eeprom_element in device.getEeprom().getcontent()["value"]: |
|
479 # get EEPROM size; <Device>-<Eeprom>-<ByteSize> |
|
480 if eeprom_element["name"] == "ByteSize": |
|
481 smartview_infos["eeprom_size"] = eeprom_element["value"] |
|
482 |
|
483 elif eeprom_element["name"] == "ConfigData": |
|
484 configData_data = self.DecimalToHex(eeprom_element["value"]) |
|
485 # get PDI type; <Device>-<Eeprom>-<ConfigData> address 0x00 |
|
486 smartview_infos["pdi_type"] = int(configData_data[0:2], 16) |
|
487 # get state of device emulation; <Device>-<Eeprom>-<ConfigData> address 0x01 |
|
488 if "{:0>8b}".format(int(configData_data[2:4], 16))[7] == '1': |
|
489 smartview_infos["device_emulation"] = "True" |
|
490 |
|
491 elif eeprom_element["name"] == "BootStrap": |
|
492 bootstrap_data = "{:0>16x}".format(eeprom_element["value"]) |
|
493 # get bootstrap configuration; <Device>-<Eeprom>-<BootStrap> |
|
494 for cfg, iter in [("mailbox_bootstrapconf_outstart", 0), |
|
495 ("mailbox_bootstrapconf_outlength", 1), |
|
496 ("mailbox_bootstrapconf_instart", 2), |
|
497 ("mailbox_bootstrapconf_inlength", 3)]: |
|
498 smartview_infos[cfg] = str(int(bootstrap_data[4*iter+2:4*(iter+1)]+bootstrap_data[4*iter:4*iter+2], 16)) |
|
499 |
|
500 # get protocol (profile) types supported by mailbox; <Device>-<Mailbox> |
|
501 for mailbox_protocol in ["VoE", "SoE", "FoE", "CoE", "EoE", "AoE"]: |
|
502 if eval("device.getMailbox().get%s()"%mailbox_protocol) is not None: |
|
503 smartview_infos["supported_mailbox"] += "%s, "%mailbox_protocol |
|
504 smartview_infos["supported_mailbox"] = smartview_infos["supported_mailbox"].strip(", ") |
|
505 |
|
506 # get standard configuration of mailbox; <Device>-<Sm> |
|
507 for sm_element in device.getSm(): |
|
508 if sm_element.getcontent() == "MBoxOut": |
|
509 smartview_infos["mailbox_standardconf_outstart"] = str(ExtractHexDecValue(sm_element.getStartAddress())) |
|
510 smartview_infos["mailbox_standardconf_outlength"] = str(ExtractHexDecValue(sm_element.getDefaultSize())) |
|
511 elif sm_element.getcontent() == "MBoxIn": |
|
512 smartview_infos["mailbox_standardconf_instart"] = str(ExtractHexDecValue(sm_element.getStartAddress())) |
|
513 smartview_infos["mailbox_standardconf_inlength"] = str(ExtractHexDecValue(sm_element.getDefaultSize())) |
|
514 else: |
|
515 pass |
|
516 |
|
517 # get device identity from <Device>-<Type> |
|
518 # vendor ID; by default, pre-defined value in self.ModulesLibrary |
|
519 # if device type in 'vendor' item equals to actual slave device type, set 'vendor_id' to vendor ID. |
|
520 for vendor_id, vendor in self.Controler.CTNParent.CTNParent.ModulesLibrary.Library.iteritems(): |
|
521 for available_device in vendor["groups"][vendor["groups"].keys()[0]]["devices"]: |
|
522 if available_device[0] == type_infos["device_type"]: |
|
523 smartview_infos["vendor_id"] = "0x" + "{:0>8x}".format(vendor_id) |
|
524 |
|
525 # product code; |
|
526 if device.getType().getProductCode() is not None: |
|
527 product_code = device.getType().getProductCode() |
|
528 smartview_infos["product_code"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(product_code)) |
|
529 |
|
530 # revision number; |
|
531 if device.getType().getRevisionNo() is not None: |
|
532 revision_no = device.getType().getRevisionNo() |
|
533 smartview_infos["revision_no"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(revision_no)) |
|
534 |
|
535 # serial number; |
|
536 if device.getType().getSerialNo() is not None: |
|
537 serial_no = device.getType().getSerialNo() |
|
538 smartview_infos["serial_no"] = "0x"+"{:0>8x}".format(ExtractHexDecValue(serial_no)) |
|
539 |
|
540 return smartview_infos |
|
541 |
|
542 else: |
|
543 return None |
|
544 |
|
545 def DecimalToHex(self, decnum): |
|
546 """ |
|
547 Convert decimal value into hexadecimal representation. |
|
548 @param decnum : decimal value |
|
549 @return hex_data : hexadecimal representation of input value in decimal |
|
550 """ |
|
551 value = "%x" % decnum |
|
552 value_len = len(value) |
|
553 if (value_len % 2) == 0: |
|
554 hex_len = value_len |
|
555 else: |
|
556 hex_len = (value_len / 2) * 2 + 2 |
|
557 |
|
558 hex_data = ("{:0>"+str(hex_len)+"x}").format(decnum) |
|
559 |
|
560 return hex_data |
|
561 |
|
562 def SiiRead(self): |
|
563 """ |
|
564 Get slave EEPROM contents maintained by master device using "ethercat sii_read -p %d" command. |
|
565 Command example : "ethercat sii_read -p 0" |
|
566 @return return_val : result of "ethercat sii_read" (binary data) |
|
567 """ |
|
568 error, return_val = self.Controler.RemoteExec(SII_READ%(self.Controler.GetSlavePos()), return_val = None) |
|
569 self.SiiData = return_val |
|
570 return return_val |
|
571 |
|
572 def SiiWrite(self, binary): |
|
573 """ |
|
574 Overwrite slave EEPROM contents using "ethercat sii_write -p %d" command. |
|
575 Command example : "ethercat sii_write -p 0 - (binary contents)" |
|
576 @param binary : EEPROM contents in binary data format |
|
577 @return return_val : result of "ethercat sii_write" (If it succeeds, the return value is NULL.) |
|
578 """ |
|
579 error, return_val = self.Controler.RemoteExec(SII_WRITE%(self.Controler.GetSlavePos()), return_val = None, sii_data = binary) |
|
580 return return_val |
|
581 |
|
582 def LoadData(self): |
|
583 """ |
|
584 Loading data from EEPROM use Sii_Read Method |
|
585 @return self.BinaryCode : slave EEPROM data in binary format (zero-padded) |
|
586 """ |
|
587 return_val = self.Controler.CommonMethod.SiiRead() |
|
588 self.BinaryCode = return_val |
|
589 self.Controler.SiiData = self.BinaryCode |
|
590 |
|
591 # append zero-filled padding data up to EEPROM size |
|
592 for index in range(self.SmartViewInfosFromXML["eeprom_size"] - len(self.BinaryCode)): |
|
593 self.BinaryCode = self.BinaryCode +'ff'.decode('hex') |
|
594 |
|
595 return self.BinaryCode |
|
596 |
|
597 def HexRead(self, binary): |
|
598 """ |
|
599 Convert binary digit representation into hexadecimal representation for "Hex View" menu. |
|
600 @param binary : binary digits |
|
601 @return hexCode : hexadecimal digits |
|
602 @return hexview_table_row, hexview_table_col : Grid size for "Hex View" UI |
|
603 """ |
|
604 row_code = [] |
|
605 row_text = "" |
|
606 row = 0 |
|
607 hex_code = [] |
|
608 |
|
609 hexview_table_col = 17 |
|
610 |
|
611 for index in range(0, len(binary)) : |
|
612 if len(binary[index]) != 1: |
|
613 break |
|
614 else: |
|
615 digithexstr = hex(ord(binary[index])) |
|
616 |
|
617 tempvar2 = digithexstr[2:4] |
|
618 if len(tempvar2) == 1: |
|
619 tempvar2 = "0" + tempvar2 |
|
620 row_code.append(tempvar2) |
|
621 |
|
622 if int(digithexstr, 16)>=32 and int(digithexstr, 16)<=126: |
|
623 row_text = row_text + chr(int(digithexstr, 16)) |
|
624 else: |
|
625 row_text = row_text + "." |
|
626 |
|
627 if index != 0 : |
|
628 if len(row_code) == (hexview_table_col - 1): |
|
629 row_code.append(row_text) |
|
630 hex_code.append(row_code) |
|
631 row_text = "" |
|
632 row_code = [] |
|
633 row = row + 1 |
|
634 |
|
635 hexview_table_row = row |
|
636 |
|
637 return hex_code, hexview_table_row, hexview_table_col |
|
638 |
|
639 def GenerateEEPROMList(self, data, direction, length): |
|
640 """ |
|
641 Generate EEPROM data list by reconstructing 'data' string. |
|
642 example : data="12345678", direction=0, length=8 -> eeprom_list=['12', '34', '56', '78'] |
|
643 data="12345678", direction=1, length=8 -> eeprom_list=['78', '56', '34', '12'] |
|
644 @param data : string to be reconstructed |
|
645 @param direction : endianness |
|
646 @param length : data length |
|
647 @return eeprom_list : reconstructed list data structure |
|
648 """ |
|
649 eeprom_list = [] |
|
650 |
|
651 if direction is 0 or 1: |
|
652 for i in range(length/2): |
|
653 if data == "": |
|
654 eeprom_list.append("00") |
|
655 else: |
|
656 eeprom_list.append(data[direction*(length-2):direction*(length-2)+2]) |
|
657 data = data[(1-direction)*2:length-direction*2] |
|
658 length -= 2 |
|
659 return eeprom_list |
|
660 |
|
661 def XmlToEeprom(self): |
|
662 """ |
|
663 Extract slave EEPROM contents using slave ESI XML file. |
|
664 - Mandatory parts |
|
665 - String category : ExtractEEPROMStringCategory() |
|
666 - General category : ExtractEEPROMGeneralCategory() |
|
667 - FMMU category : ExtractEEPROMFMMUCategory |
|
668 - SyncM category : ExtractEEPROMSyncMCategory() |
|
669 - Tx/RxPDO category : ExtractEEPROMPDOCategory() |
|
670 - DC category : ExtractEEPROMDCCategory() |
|
671 @return eeprom_binary |
|
672 """ |
|
673 eeprom = [] |
|
674 data = "" |
|
675 eeprom_size = 0 |
|
676 eeprom_binary = "" |
|
677 |
|
678 # 'device' is the slave device of the current EtherCAT slave plugin |
|
679 slave = self.Controler.CTNParent.GetSlave(self.Controler.GetSlavePos()) |
|
680 type_infos = slave.getType() |
|
681 device, alignment = self.Controler.CTNParent.GetModuleInfos(type_infos) |
|
682 |
|
683 if device is not None: |
|
684 # get ConfigData for EEPROM offset 0x0000-0x000d; <Device>-<Eeprom>-<ConfigData> |
|
685 for eeprom_element in device.getEeprom().getcontent()["value"]: |
|
686 if eeprom_element["name"] == "ConfigData": |
|
687 data = self.DecimalToHex(eeprom_element["value"]) |
|
688 eeprom += self.GenerateEEPROMList(data, 0, 28) |
|
689 |
|
690 # calculate CRC for EEPROM offset 0x000e-0x000f |
|
691 crc = 0x48 |
|
692 for segment in eeprom: |
|
693 for i in range(8): |
|
694 bit = crc & 0x80 |
|
695 crc = (crc << 1) | ((int(segment, 16) >> (7 - i)) & 0x01) |
|
696 if bit: |
|
697 crc ^= 0x07 |
|
698 for k in range(8): |
|
699 bit = crc & 0x80 |
|
700 crc <<= 1 |
|
701 if bit: |
|
702 crc ^= 0x07 |
|
703 eeprom.append(hex(crc)[len(hex(crc))-3:len(hex(crc))-1]) |
|
704 eeprom.append("00") |
|
705 |
|
706 # get VendorID for EEPROM offset 0x0010-0x0013; |
|
707 data = "" |
|
708 for vendor_id, vendor in self.Controler.CTNParent.CTNParent.ModulesLibrary.Library.iteritems(): |
|
709 for available_device in vendor["groups"][vendor["groups"].keys()[0]]["devices"]: |
|
710 if available_device[0] == type_infos["device_type"]: |
|
711 data = "{:0>8x}".format(vendor_id) |
|
712 eeprom += self.GenerateEEPROMList(data, 1, 8) |
|
713 |
|
714 # get Product Code for EEPROM offset 0x0014-0x0017; |
|
715 data = "" |
|
716 if device.getType().getProductCode() is not None: |
|
717 data = "{:0>8x}".format(ExtractHexDecValue(device.getType().getProductCode())) |
|
718 eeprom += self.GenerateEEPROMList(data, 1, 8) |
|
719 |
|
720 # get Revision Number for EEPROM offset 0x0018-0x001b; |
|
721 data = "" |
|
722 if device.getType().getRevisionNo() is not None: |
|
723 data = "{:0>8x}".format(ExtractHexDecValue(device.getType().getRevisionNo())) |
|
724 eeprom += self.GenerateEEPROMList(data, 1, 8) |
|
725 |
|
726 # get Serial Number for EEPROM 0x001c-0x001f; |
|
727 data = "" |
|
728 if device.getType().getSerialNo() is not None: |
|
729 data = "{:0>8x}".format(ExtractHexDecValue(device.getType().getSerialNo())) |
|
730 eeprom += self.GenerateEEPROMList(data, 1, 8) |
|
731 |
|
732 # get Execution Delay for EEPROM 0x0020-0x0021; not analyzed yet |
|
733 eeprom.append("00") |
|
734 eeprom.append("00") |
|
735 |
|
736 # get Port0/1 Delay for EEPROM offset 0x0022-0x0025; not analyzed yet |
|
737 eeprom.append("00") |
|
738 eeprom.append("00") |
|
739 eeprom.append("00") |
|
740 eeprom.append("00") |
|
741 |
|
742 # reserved for EEPROM offset 0x0026-0x0027; |
|
743 eeprom.append("00") |
|
744 eeprom.append("00") |
|
745 |
|
746 # get BootStrap for EEPROM offset 0x0028-0x002e; <Device>-<Eeprom>-<BootStrap> |
|
747 data = "" |
|
748 for eeprom_element in device.getEeprom().getcontent()["value"]: |
|
749 if eeprom_element["name"] == "BootStrap": |
|
750 data = "{:0>16x}".format(eeprom_element["value"]) |
|
751 eeprom += self.GenerateEEPROMList(data, 0, 16) |
|
752 |
|
753 # get Standard Mailbox for EEPROM offset 0x0030-0x0037; <Device>-<sm> |
|
754 data = "" |
|
755 for sm_element in device.getSm(): |
|
756 if sm_element.getcontent() == "MBoxOut": |
|
757 standard_receive_mailbox_offset = "{:0>4x}".format(ExtractHexDecValue(sm_element.getStartAddress())) |
|
758 standard_receive_mailbox_size = "{:0>4x}".format(ExtractHexDecValue(sm_element.getDefaultSize())) |
|
759 elif sm_element.getcontent() == "MBoxIn": |
|
760 standard_send_mailbox_offset = "{:0>4x}".format(ExtractHexDecValue(sm_element.getStartAddress())) |
|
761 standard_send_mailbox_size = "{:0>4x}".format(ExtractHexDecValue(sm_element.getDefaultSize())) |
|
762 |
|
763 if standard_receive_mailbox_offset is None: |
|
764 eeprom.append("00") |
|
765 eeprom.append("00") |
|
766 else: |
|
767 eeprom.append(standard_receive_mailbox_offset[2:4]) |
|
768 eeprom.append(standard_receive_mailbox_offset[0:2]) |
|
769 if standard_receive_mailbox_size is None: |
|
770 eeprom.append("00") |
|
771 eeprom.append("00") |
|
772 else: |
|
773 eeprom.append(standard_receive_mailbox_size[2:4]) |
|
774 eeprom.append(standard_receive_mailbox_size[0:2]) |
|
775 if standard_send_mailbox_offset is None: |
|
776 eeprom.append("00") |
|
777 eeprom.append("00") |
|
778 else: |
|
779 eeprom.append(standard_send_mailbox_offset[2:4]) |
|
780 eeprom.append(standard_send_mailbox_offset[0:2]) |
|
781 if standard_send_mailbox_size is None: |
|
782 eeprom.append("00") |
|
783 eeprom.append("00") |
|
784 else: |
|
785 eeprom.append(standard_send_mailbox_size[2:4]) |
|
786 eeprom.append(standard_send_mailbox_size[0:2]) |
|
787 |
|
788 # get supported mailbox protocols for EEPROM offset 0x0038-0x0039; |
|
789 data = 0 |
|
790 for mbox, bit in [(device.getMailbox().getAoE(), 0), |
|
791 (device.getMailbox().getEoE(), 1), |
|
792 (device.getMailbox().getCoE(), 2), |
|
793 (device.getMailbox().getFoE(), 3), |
|
794 (device.getMailbox().getSoE(), 4), |
|
795 (device.getMailbox().getVoE(), 5)]: |
|
796 if mbox is not None: |
|
797 data += 1<<bit |
|
798 data = "{:0>4x}".format(data) |
|
799 eeprom.append(data[2:4]) |
|
800 eeprom.append(data[0:2]) |
|
801 |
|
802 # resereved for EEPROM offset 0x003a-0x007b; |
|
803 for i in range(0x007b-0x003a+0x0001): |
|
804 eeprom.append("00") |
|
805 |
|
806 # get EEPROM size for EEPROM offset 0x007c-0x007d; |
|
807 data = "" |
|
808 for eeprom_element in device.getEeprom().getcontent()["value"]: |
|
809 if eeprom_element["name"] == "ByteSize": |
|
810 eeprom_size = int(str(eeprom_element["value"])) |
|
811 data = "{:0>4x}".format(int(eeprom_element["value"])/1024*8-1) |
|
812 |
|
813 if data == "": |
|
814 eeprom.append("00") |
|
815 eeprom.append("00") |
|
816 else: |
|
817 eeprom.append(data[2:4]) |
|
818 eeprom.append(data[0:2]) |
|
819 |
|
820 # Version for EEPROM 0x007e-0x007f; |
|
821 # According to "EtherCAT Slave Device Description(V0.3.0)" |
|
822 eeprom.append("01") |
|
823 eeprom.append("00") |
|
824 |
|
825 # append String Category data |
|
826 for data in self.ExtractEEPROMStringCategory(device): |
|
827 eeprom.append(data) |
|
828 |
|
829 # append General Category data |
|
830 for data in self.ExtractEEPROMGeneralCategory(device): |
|
831 eeprom.append(data) |
|
832 |
|
833 # append FMMU Category data |
|
834 for data in self.ExtractEEPROMFMMUCategory(device): |
|
835 eeprom.append(data) |
|
836 |
|
837 # append SyncM Category data |
|
838 for data in self.ExtractEEPROMSyncMCategory(device): |
|
839 eeprom.append(data) |
|
840 |
|
841 # append TxPDO Category data |
|
842 for data in self.ExtractEEPROMPDOCategory(device, "TxPdo"): |
|
843 eeprom.append(data) |
|
844 |
|
845 # append RxPDO Category data |
|
846 for data in self.ExtractEEPROMPDOCategory(device, "RxPdo"): |
|
847 eeprom.append(data) |
|
848 |
|
849 # append DC Category data |
|
850 for data in self.ExtractEEPROMDCCategory(device): |
|
851 eeprom.append(data) |
|
852 |
|
853 # append padding |
|
854 padding = eeprom_size-len(eeprom) |
|
855 for i in range(padding): |
|
856 eeprom.append("ff") |
|
857 |
|
858 # convert binary code |
|
859 for index in range(eeprom_size): |
|
860 eeprom_binary = eeprom_binary + eeprom[index].decode('hex') |
|
861 |
|
862 return eeprom_binary |
|
863 |
|
864 def ExtractEEPROMStringCategory(self, device): |
|
865 """ |
|
866 Extract "Strings" category data from slave ESI XML and generate EEPROM image data. |
|
867 @param device : 'device' object in the slave ESI XML |
|
868 @return eeprom : "Strings" category EEPROM image data |
|
869 """ |
|
870 eeprom = [] |
|
871 self.Strings = [] |
|
872 data = "" |
|
873 count = 0 # string counter |
|
874 padflag = False # padding flag if category length is odd |
|
875 |
|
876 # index information for General Category in EEPROM |
|
877 self.GroupIdx = 0 |
|
878 self.ImgIdx = 0 |
|
879 self.OrderIdx = 0 |
|
880 self.NameIdx = 0 |
|
881 |
|
882 # flag for preventing duplicated vendor specific data |
|
883 typeflag = False |
|
884 grouptypeflag = False |
|
885 groupnameflag = False |
|
886 devnameflag = False |
|
887 imageflag = False |
|
888 |
|
889 # vendor specific data |
|
890 # element1; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Type> |
|
891 # vendor_specific_data : vendor specific data (binary type) |
|
892 vendor_specific_data = "" |
|
893 # vendor_spec_strings : list of vendor specific "strings" for preventing duplicated strings |
|
894 vendor_spec_strings = [] |
|
895 for element in device.getType().getcontent(): |
|
896 data += element |
|
897 if data is not "" and type(data) == unicode: |
|
898 for vendor_spec_string in vendor_spec_strings: |
|
899 if data == vendor_spec_string: |
|
900 self.OrderIdx = vendor_spec_strings.index(data)+1 |
|
901 typeflag = True |
|
902 break |
|
903 if typeflag is False: |
|
904 count += 1 |
|
905 self.Strings.append(data) |
|
906 vendor_spec_strings.append(data) |
|
907 typeflag = True |
|
908 self.OrderIdx = count |
|
909 vendor_specific_data += "{:0>2x}".format(len(data)) |
|
910 for character in range(len(data)): |
|
911 vendor_specific_data += "{:0>2x}".format(ord(data[character])) |
|
912 data = "" |
|
913 |
|
914 # element2-1; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<GroupType> |
|
915 data = device.getGroupType() |
|
916 if data is not None and type(data) == unicode: |
|
917 for vendor_spec_string in vendor_spec_strings: |
|
918 if data == vendor_spec_string: |
|
919 self.GroupIdx = vendor_spec_strings.index(data)+1 |
|
920 grouptypeflag = True |
|
921 break |
|
922 if grouptypeflag is False: |
|
923 grouptype = data |
|
924 count += 1 |
|
925 self.Strings.append(data) |
|
926 vendor_spec_strings.append(data) |
|
927 grouptypeflag = True |
|
928 self.GroupIdx = count |
|
929 vendor_specific_data += "{:0>2x}".format(len(data)) |
|
930 for character in range(len(data)): |
|
931 vendor_specific_data += "{:0>2x}".format(ord(data[character])) |
|
932 |
|
933 # element2-2; <EtherCATInfo>-<Groups>-<Group>-<Type> |
|
934 if grouptypeflag is False: |
|
935 if self.Controler.CTNParent.CTNParent.ModulesLibrary.Library is not None: |
|
936 for vendor_id, vendor in self.Controler.CTNParent.CTNParent.ModulesLibrary.Library.iteritems(): |
|
937 for group_type, group_etc in vendor["groups"].iteritems(): |
|
938 for device_item in group_etc["devices"]: |
|
939 if device == device_item[1]: |
|
940 data = group_type |
|
941 if data is not None and type(data) == unicode: |
|
942 for vendor_spec_string in vendor_spec_strings: |
|
943 if data == vendor_spec_string: |
|
944 self.GroupIdx = vendor_spec_strings.index(data)+1 |
|
945 grouptypeflag = True |
|
946 break |
|
947 if grouptypeflag is False: |
|
948 grouptype = data |
|
949 count += 1 |
|
950 self.Strings.append(data) |
|
951 vendor_spec_strings.append(data) |
|
952 grouptypeflag = True |
|
953 self.GroupIdx = count |
|
954 vendor_specific_data += "{:0>2x}".format(len(data)) |
|
955 for character in range(len(data)): |
|
956 vendor_specific_data += "{:0>2x}".format(ord(data[character])) |
|
957 data = "" |
|
958 |
|
959 # element3; <EtherCATInfo>-<Descriptions>-<Groups>-<Group>-<Name(LcId is "1033")> |
|
960 if self.Controler.CTNParent.CTNParent.ModulesLibrary.Library is not None: |
|
961 for vendorId, vendor in self.Controler.CTNParent.CTNParent.ModulesLibrary.Library.iteritems(): |
|
962 for group_type, group_etc in vendor["groups"].iteritems(): |
|
963 for device_item in group_etc["devices"]: |
|
964 if device == device_item[1]: |
|
965 data = group_etc["name"] |
|
966 if data is not "" and type(data) == unicode: |
|
967 for vendor_spec_string in vendor_spec_strings: |
|
968 if data == vendor_spec_string: |
|
969 groupnameflag = True |
|
970 break |
|
971 if groupnameflag is False: |
|
972 count += 1 |
|
973 self.Strings.append(data) |
|
974 vendor_spec_strings.append(data) |
|
975 groupnameflag = True |
|
976 vendor_specific_data += "{:0>2x}".format(len(data)) |
|
977 for character in range(len(data)): |
|
978 vendor_specific_data += "{:0>2x}".format(ord(data[character])) |
|
979 data = "" |
|
980 |
|
981 # element4; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Name(LcId is "1033" or "1"?)> |
|
982 for element in device.getName(): |
|
983 if element.getLcId() == 1 or element.getLcId()==1033: |
|
984 data = element.getcontent() |
|
985 if data is not "" and type(data) == unicode: |
|
986 for vendor_spec_string in vendor_spec_strings: |
|
987 if data == vendor_spec_string: |
|
988 self.NameIdx = vendor_spec_strings.index(data)+1 |
|
989 devnameflag = True |
|
990 break |
|
991 if devnameflag is False: |
|
992 count += 1 |
|
993 self.Strings.append(data) |
|
994 vendor_spec_strings.append(data) |
|
995 devnameflag = True |
|
996 self.NameIdx = count |
|
997 vendor_specific_data += "{:0>2x}".format(len(data)) |
|
998 for character in range(len(data)): |
|
999 vendor_specific_data += "{:0>2x}".format(ord(data[character])) |
|
1000 data = "" |
|
1001 |
|
1002 # element5-1; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Image16x14> |
|
1003 if device.getcontent() is not None: |
|
1004 data = device.getcontent()["value"] |
|
1005 if data is not None and type(data) == unicode: |
|
1006 for vendor_spec_string in vendor_spec_strings: |
|
1007 if data == vendor_spec_string: |
|
1008 self.ImgIdx = vendor_spec_strings.index(data)+1 |
|
1009 imageflag = True |
|
1010 break |
|
1011 if imageflag is False: |
|
1012 count += 1 |
|
1013 self.Strings.append(data) |
|
1014 vendor_spec_strings.append(data) |
|
1015 imageflag = True |
|
1016 self.ImgIdx = count |
|
1017 vendor_specific_data += "{:0>2x}".format(len(data)) |
|
1018 for character in range(len(data)): |
|
1019 vendor_specific_data += "{:0>2x}".format(ord(data[character])) |
|
1020 |
|
1021 # element5-2; <EtherCATInfo>-<Descriptions>-<Groups>-<Group>-<Image16x14> |
|
1022 if imageflag is False: |
|
1023 if self.Controler.CTNParent.CTNParent.ModulesLibrary.Library is not None: |
|
1024 for vendor_id, vendor in self.Controler.CTNParent.CTNParent.ModulesLibrary.Library.iteritems(): |
|
1025 for group_type, group_etc in vendor["groups"].iteritems(): |
|
1026 for device_item in group_etc["devices"]: |
|
1027 if device == device_item[1]: |
|
1028 data = group_etc["value"] |
|
1029 if data is not None and type(data) == unicode: |
|
1030 for vendor_spec_string in vendor_spec_strings: |
|
1031 if data == vendor_spec_string: |
|
1032 self.ImgIdx = vendor_spec_strings.index(data)+1 |
|
1033 imageflag = True |
|
1034 break |
|
1035 if imageflag is False: |
|
1036 count += 1 |
|
1037 self.Strings.append(data) |
|
1038 vendor_spec_strings.append(data) |
|
1039 imageflag = True |
|
1040 self.ImgIdx = count |
|
1041 vendor_specific_data += "{:0>2x}".format(len(data)) |
|
1042 for character in range(len(data)): |
|
1043 vendor_specific_data += "{:0>2x}".format(ord(data[character])) |
|
1044 data = "" |
|
1045 |
|
1046 # DC related elements |
|
1047 # <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Dc>-<OpMode>-<Name> |
|
1048 dc_related_elements = "" |
|
1049 if device.getDc() is not None: |
|
1050 for element in device.getDc().getOpMode(): |
|
1051 data = element.getName() |
|
1052 if data is not "": |
|
1053 count += 1 |
|
1054 self.Strings.append(data) |
|
1055 dc_related_elements += "{:0>2x}".format(len(data)) |
|
1056 for character in range(len(data)): |
|
1057 dc_related_elements += "{:0>2x}".format(ord(data[character])) |
|
1058 data = "" |
|
1059 |
|
1060 # Input elements(TxPDO) |
|
1061 # <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<TxPdo>; Name |
|
1062 input_elements = "" |
|
1063 inputs = [] |
|
1064 for element in device.getTxPdo(): |
|
1065 for name in element.getName(): |
|
1066 data = name.getcontent() |
|
1067 for input in inputs: |
|
1068 if data == input: |
|
1069 data = "" |
|
1070 if data is not "": |
|
1071 count += 1 |
|
1072 self.Strings.append(data) |
|
1073 inputs.append(data) |
|
1074 input_elements += "{:0>2x}".format(len(data)) |
|
1075 for character in range(len(data)): |
|
1076 input_elements += "{:0>2x}".format(ord(data[character])) |
|
1077 data = "" |
|
1078 for entry in element.getEntry(): |
|
1079 for name in entry.getName(): |
|
1080 data = name.getcontent() |
|
1081 for input in inputs: |
|
1082 if data == input: |
|
1083 data = "" |
|
1084 if data is not "": |
|
1085 count += 1 |
|
1086 self.Strings.append(data) |
|
1087 inputs.append(data) |
|
1088 input_elements += "{:0>2x}".format(len(data)) |
|
1089 for character in range(len(data)): |
|
1090 input_elements += "{:0>2x}".format(ord(data[character])) |
|
1091 data = "" |
|
1092 |
|
1093 # Output elements(RxPDO) |
|
1094 # <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<RxPdo>; Name |
|
1095 output_elements = "" |
|
1096 outputs = [] |
|
1097 for element in device.getRxPdo(): |
|
1098 for name in element.getName(): |
|
1099 data = name.getcontent() |
|
1100 for output in outputs: |
|
1101 if data == output: |
|
1102 data = "" |
|
1103 if data is not "": |
|
1104 count += 1 |
|
1105 self.Strings.append(data) |
|
1106 outputs.append(data) |
|
1107 output_elements += "{:0>2x}".format(len(data)) |
|
1108 for character in range(len(data)): |
|
1109 output_elements += "{:0>2x}".format(ord(data[character])) |
|
1110 data = "" |
|
1111 for entry in element.getEntry(): |
|
1112 for name in entry.getName(): |
|
1113 data = name.getcontent() |
|
1114 for output in outputs: |
|
1115 if data == output: |
|
1116 data = "" |
|
1117 if data is not "": |
|
1118 count += 1 |
|
1119 self.Strings.append(data) |
|
1120 outputs.append(data) |
|
1121 output_elements += "{:0>2x}".format(len(data)) |
|
1122 for character in range(len(data)): |
|
1123 output_elements += "{:0>2x}".format(ord(data[character])) |
|
1124 data = "" |
|
1125 |
|
1126 # form eeprom data |
|
1127 # category header |
|
1128 eeprom.append("0a") |
|
1129 eeprom.append("00") |
|
1130 # category length (word); 1 word is 4 bytes. "+2" is the length of string's total number |
|
1131 length = len(vendor_specific_data + dc_related_elements + input_elements + output_elements) + 2 |
|
1132 if length%4 == 0: |
|
1133 pass |
|
1134 else: |
|
1135 length +=length%4 |
|
1136 padflag = True |
|
1137 eeprom.append("{:0>4x}".format(length/4)[2:4]) |
|
1138 eeprom.append("{:0>4x}".format(length/4)[0:2]) |
|
1139 # total numbers of strings |
|
1140 eeprom.append("{:0>2x}".format(count)) |
|
1141 for element in [vendor_specific_data, |
|
1142 dc_related_elements, |
|
1143 input_elements, |
|
1144 output_elements]: |
|
1145 for iter in range(len(element)/2): |
|
1146 if element == "": |
|
1147 eeprom.append("00") |
|
1148 else: |
|
1149 eeprom.append(element[0:2]) |
|
1150 element = element[2:len(element)] |
|
1151 # padding if length is odd bytes |
|
1152 if padflag is True: |
|
1153 eeprom.append("ff") |
|
1154 |
|
1155 return eeprom |
|
1156 |
|
1157 def ExtractEEPROMGeneralCategory(self, device): |
|
1158 """ |
|
1159 Extract "General" category data from slave ESI XML and generate EEPROM image data. |
|
1160 @param device : 'device' object in the slave ESI XML |
|
1161 @return eeprom : "Strings" category EEPROM image data |
|
1162 """ |
|
1163 eeprom = [] |
|
1164 data = "" |
|
1165 |
|
1166 # category header |
|
1167 eeprom.append("1e") |
|
1168 eeprom.append("00") |
|
1169 |
|
1170 # category length |
|
1171 eeprom.append("10") |
|
1172 eeprom.append("00") |
|
1173 |
|
1174 # word 1 : Group Type index and Image index in STRINGS Category |
|
1175 eeprom.append("{:0>2x}".format(self.GroupIdx)) |
|
1176 eeprom.append("{:0>2x}".format(self.ImgIdx)) |
|
1177 |
|
1178 # word 2 : Device Type index and Device Name index in STRINGS Category |
|
1179 eeprom.append("{:0>2x}".format(self.OrderIdx)) |
|
1180 eeprom.append("{:0>2x}".format(self.NameIdx)) |
|
1181 |
|
1182 # word 3 : Physical Layer Port info. and CoE Details |
|
1183 eeprom.append("01") # Physical Layer Port info - assume 01 |
|
1184 # CoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE> |
|
1185 coe_details = 0 |
|
1186 if device.getMailbox().getCoE() is not None: |
|
1187 coe_details = 1 # sdo enabled |
|
1188 for attr, bit in [(device.getMailbox().getCoE().getSdoInfo(), 1), |
|
1189 (device.getMailbox().getCoE().getPdoAssign(), 2), |
|
1190 (device.getMailbox().getCoE().getPdoConfig(), 3), |
|
1191 (device.getMailbox().getCoE().getPdoUpload(), 4), |
|
1192 (device.getMailbox().getCoE().getCompleteAccess(), 5)]: |
|
1193 if attr==1 or attr==True: |
|
1194 coe_details += 1<<bit |
|
1195 eeprom.append("{:0>2x}".format(coe_details)) |
|
1196 |
|
1197 # word 4 : FoE Details and EoE Details |
|
1198 # FoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<FoE> |
|
1199 if device.getMailbox().getFoE() is not None: |
|
1200 eeprom.append("01") |
|
1201 else: |
|
1202 eeprom.append("00") |
|
1203 # EoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<EoE> |
|
1204 if device.getMailbox().getEoE() is not None: |
|
1205 eeprom.append("01") |
|
1206 else: |
|
1207 eeprom.append("00") |
|
1208 |
|
1209 # word 5 : SoE Channels(reserved) and DS402 Channels |
|
1210 # SoE Details; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<SoE> |
|
1211 if device.getMailbox().getSoE() is not None: |
|
1212 eeprom.append("01") |
|
1213 else: |
|
1214 eeprom.append("00") |
|
1215 # DS402Channels; <EtherCATInfo>-<Descriptions>-<Devices>-<Device>-<Mailbox>-<CoE>: DS402Channels |
|
1216 if device.getMailbox().getCoE().getDS402Channels() == True \ |
|
1217 or device.getMailbox().getCoE().getDS402Channels() == 1: |
|
1218 eeprom.append("01") |
|
1219 else: |
|
1220 eeprom.append("00") |
|
1221 |
|
1222 # word 6 : SysmanClass(reserved) and Flags |
|
1223 eeprom.append("00") # reserved |
|
1224 # Flags |
|
1225 en_safeop = False |
|
1226 en_lrw = False |
|
1227 if device.getType().getTcCfgModeSafeOp() == True \ |
|
1228 or device.getType().getTcCfgModeSafeOp() == 1: |
|
1229 en_safeop = True |
|
1230 if device.getType().getUseLrdLwr() == True \ |
|
1231 or device.getType().getUseLrdLwr() == 1: |
|
1232 en_lrw = True |
|
1233 |
|
1234 flags = "0b"+"000000"+str(int(en_lrw))+str(int(en_safeop)) |
|
1235 eeprom.append("{:0>2x}".format(int(flags, 2))) |
|
1236 |
|
1237 # word 7 : Current On EBus (assume 0x0000) |
|
1238 eeprom.append("00") |
|
1239 eeprom.append("00") |
|
1240 # after word 7; couldn't analyze yet |
|
1241 eeprom.append("03") |
|
1242 eeprom.append("00") |
|
1243 eeprom.append("11") |
|
1244 eeprom.append("00") |
|
1245 eeprom.append("00") |
|
1246 eeprom.append("00") |
|
1247 eeprom.append("00") |
|
1248 eeprom.append("00") |
|
1249 eeprom.append("00") |
|
1250 eeprom.append("00") |
|
1251 eeprom.append("00") |
|
1252 eeprom.append("00") |
|
1253 eeprom.append("00") |
|
1254 eeprom.append("00") |
|
1255 eeprom.append("00") |
|
1256 eeprom.append("00") |
|
1257 eeprom.append("00") |
|
1258 eeprom.append("00") |
|
1259 |
|
1260 return eeprom |
|
1261 |
|
1262 def ExtractEEPROMFMMUCategory(self, device): |
|
1263 """ |
|
1264 Extract "FMMU" category data from slave ESI XML and generate EEPROM image data. |
|
1265 @param device : 'device' object in the slave ESI XML |
|
1266 @return eeprom : "Strings" category EEPROM image data |
|
1267 """ |
|
1268 eeprom = [] |
|
1269 data = "" |
|
1270 count = 0 # number of FMMU |
|
1271 padflag = False |
|
1272 |
|
1273 for fmmu in device.getFmmu(): |
|
1274 count += 1 |
|
1275 if fmmu.getcontent() == "Outputs": |
|
1276 data += "01" |
|
1277 if fmmu.getcontent() == "Inputs": |
|
1278 data += "02" |
|
1279 if fmmu.getcontent() == "MBoxState": |
|
1280 data += "03" |
|
1281 |
|
1282 # construct of EEPROM data |
|
1283 if data is not "": |
|
1284 # category header |
|
1285 eeprom.append("28") |
|
1286 eeprom.append("00") |
|
1287 # category length |
|
1288 if count%2 == 1: |
|
1289 padflag = True |
|
1290 eeprom.append("{:0>4x}".format((count+1)/2)[2:4]) |
|
1291 eeprom.append("{:0>4x}".format((count+1)/2)[0:2]) |
|
1292 else: |
|
1293 eeprom.append("{:0>4x}".format((count)/2)[2:4]) |
|
1294 eeprom.append("{:0>4x}".format((count)/2)[0:2]) |
|
1295 for i in range(count): |
|
1296 if data == "": |
|
1297 eeprom.append("00") |
|
1298 else: |
|
1299 eeprom.append(data[0:2]) |
|
1300 data = data[2:len(data)] |
|
1301 # padding if length is odd bytes |
|
1302 if padflag is True: |
|
1303 eeprom.append("ff") |
|
1304 |
|
1305 return eeprom |
|
1306 |
|
1307 def ExtractEEPROMSyncMCategory(self, device): |
|
1308 """ |
|
1309 Extract "SyncM" category data from slave ESI XML and generate EEPROM image data. |
|
1310 @param device : 'device' object in the slave ESI XML |
|
1311 @return eeprom : "Strings" category EEPROM image data |
|
1312 """ |
|
1313 eeprom = [] |
|
1314 data = "" |
|
1315 number = {"MBoxOut":"01", "MBoxIn":"02", "Outputs":"03", "Inputs":"04"} |
|
1316 |
|
1317 for sm in device.getSm(): |
|
1318 for attr in [sm.getStartAddress(), |
|
1319 sm.getDefaultSize(), |
|
1320 sm.getControlByte()]: |
|
1321 if attr is not None: |
|
1322 data += "{:0>4x}".format(ExtractHexDecValue(attr))[2:4] |
|
1323 data += "{:0>4x}".format(ExtractHexDecValue(attr))[0:2] |
|
1324 else: |
|
1325 data += "0000" |
|
1326 if sm.getEnable() == "1" or sm.getEnable() == True: |
|
1327 data += "01" |
|
1328 else: |
|
1329 data += "00" |
|
1330 data += number[sm.getcontent()] |
|
1331 |
|
1332 if data is not "": |
|
1333 # category header |
|
1334 eeprom.append("29") |
|
1335 eeprom.append("00") |
|
1336 # category length |
|
1337 eeprom.append("{:0>4x}".format(len(data)/4)[2:4]) |
|
1338 eeprom.append("{:0>4x}".format(len(data)/4)[0:2]) |
|
1339 for i in range(len(data)/2): |
|
1340 if data == "": |
|
1341 eeprom.append("00") |
|
1342 else: |
|
1343 eeprom.append(data[0:2]) |
|
1344 data = data[2:len(data)] |
|
1345 |
|
1346 return eeprom |
|
1347 |
|
1348 def ExtractEEPROMPDOCategory(self, device, pdotype): |
|
1349 """ |
|
1350 Extract ""PDO (Tx, Rx)"" category data from slave ESI XML and generate EEPROM image data. |
|
1351 @param device : 'device' object in the slave ESI XML |
|
1352 @param pdotype : identifier whether "TxPDO" or "RxPDO". |
|
1353 @return eeprom : "Strings" category EEPROM image data |
|
1354 """ |
|
1355 eeprom = [] |
|
1356 data = "" |
|
1357 count = 0 |
|
1358 en_fixed = False |
|
1359 en_mandatory = False |
|
1360 en_virtual = False |
|
1361 |
|
1362 for element in eval("device.get%s()"%pdotype): |
|
1363 # PDO Index |
|
1364 data += "{:0>4x}".format(ExtractHexDecValue(element.getIndex().getcontent()))[2:4] |
|
1365 data += "{:0>4x}".format(ExtractHexDecValue(element.getIndex().getcontent()))[0:2] |
|
1366 # Number of Entries |
|
1367 data += "{:0>2x}".format(len(element.getEntry())) |
|
1368 # About Sync Manager |
|
1369 if element.getSm() is not None: |
|
1370 data += "{:0>2x}".format(element.getSm()) |
|
1371 else: |
|
1372 data += "ff" |
|
1373 # Reference to DC Synch (according to ET1100 documentation) - assume 0 |
|
1374 data += "00" |
|
1375 # Name Index |
|
1376 objname = "" |
|
1377 for name in element.getName(): |
|
1378 objname = name.getcontent() |
|
1379 for name in self.Strings: |
|
1380 count += 1 |
|
1381 if objname == name: |
|
1382 break |
|
1383 if len(self.Strings)+1 == count: |
|
1384 data += "00" |
|
1385 else: |
|
1386 data += "{:0>2x}".format(count) |
|
1387 count = 0 |
|
1388 # Flags; by Fixed, Mandatory, Virtual attributes ? |
|
1389 if element.getFixed() == True or 1: |
|
1390 en_fixed = True |
|
1391 if element.getMandatory() == True or 1: |
|
1392 en_mandatory = True |
|
1393 if element.getVirtual() == True or element.getVirtual(): |
|
1394 en_virtual = True |
|
1395 data += str(int(en_fixed)) + str(int(en_mandatory)) + str(int(en_virtual)) + "0" |
|
1396 |
|
1397 for entry in element.getEntry(): |
|
1398 # Entry Index |
|
1399 data += "{:0>4x}".format(ExtractHexDecValue(entry.getIndex().getcontent()))[2:4] |
|
1400 data += "{:0>4x}".format(ExtractHexDecValue(entry.getIndex().getcontent()))[0:2] |
|
1401 # Subindex |
|
1402 data += "{:0>2x}".format(int(entry.getSubIndex())) |
|
1403 # Entry Name Index |
|
1404 objname = "" |
|
1405 for name in entry.getName(): |
|
1406 objname = name.getcontent() |
|
1407 for name in self.Strings: |
|
1408 count += 1 |
|
1409 if objname == name: |
|
1410 break |
|
1411 if len(self.Strings)+1 == count: |
|
1412 data += "00" |
|
1413 else: |
|
1414 data += "{:0>2x}".format(count) |
|
1415 count = 0 |
|
1416 # DataType |
|
1417 if entry.getDataType() is not None: |
|
1418 if entry.getDataType().getcontent() in self.BaseDataTypeDict: |
|
1419 data += self.BaseDataTypeDict[entry.getDataType().getcontent()] |
|
1420 else: |
|
1421 data += "00" |
|
1422 else: |
|
1423 data += "00" |
|
1424 # BitLen |
|
1425 if entry.getBitLen() is not None: |
|
1426 data += "{:0>2x}".format(int(entry.getBitLen())) |
|
1427 else: |
|
1428 data += "00" |
|
1429 # Flags; by Fixed attributes ? |
|
1430 en_fixed = False |
|
1431 if entry.getFixed() == True or entry.getFixed() == 1: |
|
1432 en_fixed = True |
|
1433 data += str(int(en_fixed)) + "000" |
|
1434 |
|
1435 if data is not "": |
|
1436 # category header |
|
1437 if pdotype == "TxPdo": |
|
1438 eeprom.append("32") |
|
1439 elif pdotype == "RxPdo": |
|
1440 eeprom.append("33") |
|
1441 else: |
|
1442 eeprom.append("00") |
|
1443 eeprom.append("00") |
|
1444 # category length |
|
1445 eeprom.append("{:0>4x}".format(len(data)/4)[2:4]) |
|
1446 eeprom.append("{:0>4x}".format(len(data)/4)[0:2]) |
|
1447 data = str(data.lower()) |
|
1448 for i in range(len(data)/2): |
|
1449 if data == "": |
|
1450 eeprom.append("00") |
|
1451 else: |
|
1452 eeprom.append(data[0:2]) |
|
1453 data = data[2:len(data)] |
|
1454 |
|
1455 return eeprom |
|
1456 |
|
1457 def ExtractEEPROMDCCategory(self, device): |
|
1458 """ |
|
1459 Extract "DC(Distributed Clock)" category data from slave ESI XML and generate EEPROM image data. |
|
1460 @param device : 'device' object in the slave ESI XML |
|
1461 @return eeprom : "Strings" category EEPROM image data |
|
1462 """ |
|
1463 eeprom = [] |
|
1464 data = "" |
|
1465 count = 0 |
|
1466 namecount = 0 |
|
1467 |
|
1468 if device.getDc() is not None: |
|
1469 for element in device.getDc().getOpMode(): |
|
1470 count += 1 |
|
1471 # assume that word 1-7 are 0x0000 |
|
1472 data += "0000" |
|
1473 data += "0000" |
|
1474 data += "0000" |
|
1475 data += "0000" |
|
1476 data += "0000" |
|
1477 data += "0000" |
|
1478 data += "0000" |
|
1479 # word 8-10 |
|
1480 # AssignActivate |
|
1481 if element.getAssignActivate() is not None: |
|
1482 data += "{:0>4x}".format(ExtractHexDecValue(element.getAssignActivate()))[2:4] |
|
1483 data += "{:0>4x}".format(ExtractHexDecValue(element.getAssignActivate()))[0:2] |
|
1484 else: |
|
1485 data += "0000" |
|
1486 # Factor of CycleTimeSync0 ? and default is 1? |
|
1487 if element.getCycleTimeSync0() is not None: |
|
1488 if element.getCycleTimeSync0().getFactor() is not None: |
|
1489 data += "{:0>2x}".format(int(element.getCycleTimeSync0().getFactor())) |
|
1490 data += "00" |
|
1491 else: |
|
1492 data += "0100" |
|
1493 else: |
|
1494 data += "0100" |
|
1495 # Index of Name in STRINGS Category |
|
1496 # Name Index |
|
1497 objname = "" |
|
1498 for name in element.getName(): |
|
1499 objname += name |
|
1500 for name in self.Strings: |
|
1501 namecount += 1 |
|
1502 if objname == name: |
|
1503 break |
|
1504 if len(self.Strings)+1 == namecount: |
|
1505 data += "00" |
|
1506 else: |
|
1507 data += "{:0>2x}".format(namecount) |
|
1508 namecount = 0 |
|
1509 data += "00" |
|
1510 # assume that word 11-12 are 0x0000 |
|
1511 data += "0000" |
|
1512 data += "0000" |
|
1513 |
|
1514 if data is not "": |
|
1515 # category header |
|
1516 eeprom.append("3c") |
|
1517 eeprom.append("00") |
|
1518 # category length |
|
1519 eeprom.append("{:0>4x}".format(len(data)/4)[2:4]) |
|
1520 eeprom.append("{:0>4x}".format(len(data)/4)[0:2]) |
|
1521 data = str(data.lower()) |
|
1522 for i in range(len(data)/2): |
|
1523 if data == "": |
|
1524 eeprom.append("00") |
|
1525 else: |
|
1526 eeprom.append(data[0:2]) |
|
1527 data = data[2:len(data)] |
|
1528 |
|
1529 return eeprom |
|
1530 |
|
1531 #------------------------------------------------------------------------------- |
|
1532 # Used Register Access |
|
1533 #------------------------------------------------------------------------------- |
|
1534 def RegRead(self, offset, length): |
|
1535 """ |
|
1536 Read slave ESC register content using "ethercat reg_read -p %d %s %s" command. |
|
1537 Command example : "ethercat reg_read -p 0 0x0c00 0x0400" |
|
1538 @param offset : register address |
|
1539 @param length : register length |
|
1540 @return return_val : register data |
|
1541 """ |
|
1542 error, return_val = self.Controler.RemoteExec(REG_READ%(self.Controler.GetSlavePos(), offset, length), return_val = None) |
|
1543 return return_val |
|
1544 |
|
1545 def RegWrite(self, address, data): |
|
1546 """ |
|
1547 Write data to slave ESC register using "ethercat reg_write -p %d %s %s" command. |
|
1548 Command example : "ethercat reg_write -p 0 0x0c04 0x0001" |
|
1549 @param address : register address |
|
1550 @param data : data to write |
|
1551 @return return_val : the execution result of "ethercat reg_write" (for error check) |
|
1552 """ |
|
1553 error, return_val = self.Controler.RemoteExec(REG_WRITE%(self.Controler.GetSlavePos(), address, data), return_val = None) |
|
1554 return return_val |
|
1555 |
|
1556 def Rescan(self): |
|
1557 """ |
|
1558 Synchronize EEPROM data in master controller with the data in slave device after EEPROM write. |
|
1559 Command example : "ethercat rescan -p 0" |
|
1560 """ |
|
1561 error, return_val = self.Controler.RemoteExec(RESCAN%(self.Controler.GetSlavePos()), return_val = None) |
|
1562 |
|
1563 #------------------------------------------------------------------------------- |
|
1564 # Common Use Methods |
|
1565 #------------------------------------------------------------------------------- |
|
1566 def CheckConnect(self, cyclic_flag): |
|
1567 """ |
|
1568 Check connection status (1) between Beremiz and the master (2) between the master and the slave. |
|
1569 @param cyclic_flag: 0 - one shot, 1 - periodic |
|
1570 @return True or False |
|
1571 """ |
|
1572 if self.Controler.GetCTRoot()._connector is not None: |
|
1573 # Check connection between the master and the slave. |
|
1574 # Command example : "ethercat xml -p 0" |
|
1575 error, return_val = self.Controler.RemoteExec(SLAVE_XML%(self.Controler.GetSlavePos()), return_val = None) |
|
1576 number_of_lines = return_val.split("\n") |
|
1577 if len(number_of_lines) <= 2 : # No slave connected to the master controller |
|
1578 if not cyclic_flag : |
|
1579 self.CreateErrorDialog('No connected slaves') |
|
1580 return False |
|
1581 |
|
1582 elif len(number_of_lines) > 2 : |
|
1583 return True |
|
1584 else: |
|
1585 # The master controller is not connected to Beremiz host |
|
1586 if not cyclic_flag : |
|
1587 self.CreateErrorDialog('PLC not connected!') |
|
1588 return False |
|
1589 |
|
1590 def CreateErrorDialog(self, mention): |
|
1591 """ |
|
1592 Create a dialog to indicate error or warning. |
|
1593 @param mention : Error String |
|
1594 """ |
|
1595 app_frame = self.Controler.GetCTRoot().AppFrame |
|
1596 dlg = wx.MessageDialog (app_frame, mention, |
|
1597 ' Warning...', |
|
1598 wx.OK | wx.ICON_INFORMATION) |
|
1599 dlg.ShowModal() |
|
1600 dlg.Destroy() |