|
1 #!/usr/bin/env python |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 # This file is part of Beremiz |
|
5 # |
|
6 # Copyright (C) 2011-2014: Laurent BESSARD, Edouard TISSERANT |
|
7 # RTES Lab : CRKim, JBLee, youcu |
|
8 # Higen Motor : Donggu Kang |
|
9 # |
|
10 # See COPYING file for copyrights details. |
|
11 |
|
12 import os, shutil |
|
13 from lxml import etree |
|
14 |
|
15 import wx |
|
16 import csv |
|
17 |
|
18 from xmlclass import * |
|
19 |
|
20 from ConfigTreeNode import ConfigTreeNode, XSDSchemaErrorMessage |
|
21 from PLCControler import UndoBuffer, LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY |
|
22 |
|
23 from EthercatSlave import ExtractHexDecValue, ExtractName |
|
24 from EthercatMaster import _EthercatCTN |
|
25 from ConfigEditor import LibraryEditor, ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE |
|
26 |
|
27 ScriptDirectory = os.path.split(os.path.realpath(__file__))[0] |
|
28 |
|
29 #-------------------------------------------------- |
|
30 # Ethercat ConfNode |
|
31 #-------------------------------------------------- |
|
32 |
|
33 EtherCATInfoParser = GenerateParserFromXSD(os.path.join(os.path.dirname(__file__), "EtherCATInfo.xsd")) |
|
34 EtherCATInfo_XPath = lambda xpath: etree.XPath(xpath) |
|
35 |
|
36 def HexDecValue(context, *args): |
|
37 return str(ExtractHexDecValue(args[0][0])) |
|
38 |
|
39 def EntryName(context, *args): |
|
40 return ExtractName(args[0], |
|
41 args[1][0] if len(args) > 1 else None) |
|
42 |
|
43 ENTRY_INFOS_KEYS = [ |
|
44 ("Index", lambda x: "#x%4.4X" % int(x), "#x0000"), |
|
45 ("SubIndex", str, "0"), |
|
46 ("Name", str, ""), |
|
47 ("Type", str, ""), |
|
48 ("BitSize", int, 0), |
|
49 ("Access", str, ""), |
|
50 ("PDOMapping", str, ""), |
|
51 ("PDO index", str, ""), |
|
52 ("PDO name", str, ""), |
|
53 ("PDO type", str, "")] |
|
54 |
|
55 class EntryListFactory: |
|
56 |
|
57 def __init__(self, entries): |
|
58 self.Entries = entries |
|
59 |
|
60 def AddEntry(self, context, *args): |
|
61 index, subindex = map(lambda x: int(x[0]), args[:2]) |
|
62 new_entry_infos = { |
|
63 key: translate(arg[0]) if len(arg) > 0 else default |
|
64 for (key, translate, default), arg |
|
65 in zip(ENTRY_INFOS_KEYS, args)} |
|
66 |
|
67 if (index, subindex) != (0, 0): |
|
68 entry_infos = self.Entries.get((index, subindex)) |
|
69 if entry_infos is not None: |
|
70 for param in ["PDO index", "PDO name", "PDO type"]: |
|
71 value = new_entry_infos.get(param) |
|
72 if value is not None: |
|
73 entry_infos[param] = value |
|
74 else: |
|
75 self.Entries[(index, subindex)] = new_entry_infos |
|
76 |
|
77 entries_list_xslt = etree.parse( |
|
78 os.path.join(ScriptDirectory, "entries_list.xslt")) |
|
79 |
|
80 cls = EtherCATInfoParser.GetElementClass("DeviceType") |
|
81 if cls: |
|
82 |
|
83 profile_numbers_xpath = EtherCATInfo_XPath("Profile/ProfileNo") |
|
84 def GetProfileNumbers(self): |
|
85 return [number.text for number in profile_numbers_xpath(self)] |
|
86 setattr(cls, "GetProfileNumbers", GetProfileNumbers) |
|
87 |
|
88 def getCoE(self): |
|
89 mailbox = self.getMailbox() |
|
90 if mailbox is not None: |
|
91 return mailbox.getCoE() |
|
92 return None |
|
93 setattr(cls, "getCoE", getCoE) |
|
94 |
|
95 def GetEntriesList(self, limits=None): |
|
96 entries = {} |
|
97 |
|
98 factory = EntryListFactory(entries) |
|
99 |
|
100 entries_list_xslt_tree = etree.XSLT( |
|
101 entries_list_xslt, extensions = { |
|
102 ("entries_list_ns", "AddEntry"): factory.AddEntry, |
|
103 ("entries_list_ns", "HexDecValue"): HexDecValue, |
|
104 ("entries_list_ns", "EntryName"): EntryName}) |
|
105 entries_list_xslt_tree(self, **dict(zip( |
|
106 ["min_index", "max_index"], |
|
107 map(lambda x: etree.XSLT.strparam(str(x)), |
|
108 limits if limits is not None else [0x0000, 0xFFFF]) |
|
109 ))) |
|
110 |
|
111 return entries |
|
112 setattr(cls, "GetEntriesList", GetEntriesList) |
|
113 |
|
114 def GetSyncManagers(self): |
|
115 sync_managers = [] |
|
116 for sync_manager in self.getSm(): |
|
117 sync_manager_infos = {} |
|
118 for name, value in [("Name", sync_manager.getcontent()), |
|
119 ("Start Address", sync_manager.getStartAddress()), |
|
120 ("Default Size", sync_manager.getDefaultSize()), |
|
121 ("Control Byte", sync_manager.getControlByte()), |
|
122 ("Enable", sync_manager.getEnable())]: |
|
123 if value is None: |
|
124 value ="" |
|
125 sync_manager_infos[name] = value |
|
126 sync_managers.append(sync_manager_infos) |
|
127 return sync_managers |
|
128 setattr(cls, "GetSyncManagers", GetSyncManagers) |
|
129 |
|
130 def GroupItemCompare(x, y): |
|
131 if x["type"] == y["type"]: |
|
132 if x["type"] == ETHERCAT_GROUP: |
|
133 return cmp(x["order"], y["order"]) |
|
134 else: |
|
135 return cmp(x["name"], y["name"]) |
|
136 elif x["type"] == ETHERCAT_GROUP: |
|
137 return -1 |
|
138 return 1 |
|
139 |
|
140 def SortGroupItems(group): |
|
141 for item in group["children"]: |
|
142 if item["type"] == ETHERCAT_GROUP: |
|
143 SortGroupItems(item) |
|
144 group["children"].sort(GroupItemCompare) |
|
145 |
|
146 class ModulesLibrary: |
|
147 |
|
148 MODULES_EXTRA_PARAMS = [ |
|
149 ("pdo_alignment", { |
|
150 "column_label": _("PDO alignment"), |
|
151 "column_size": 150, |
|
152 "default": 8, |
|
153 "description": _( |
|
154 "Minimal size in bits between 2 pdo entries")}), |
|
155 ("max_pdo_size", { |
|
156 "column_label": _("Max entries by PDO"), |
|
157 "column_size": 150, |
|
158 "default": 255, |
|
159 "description": _( |
|
160 """Maximal number of entries mapped in a PDO |
|
161 including empty entries used for PDO alignment""")}), |
|
162 ("add_pdo", { |
|
163 "column_label": _("Creating new PDO"), |
|
164 "column_size": 150, |
|
165 "default": 0, |
|
166 "description": _( |
|
167 """Adding a PDO not defined in default configuration |
|
168 for mapping needed location variables |
|
169 (1 if possible)""")}) |
|
170 ] |
|
171 |
|
172 def __init__(self, path, parent_library=None): |
|
173 self.Path = path |
|
174 if not os.path.exists(self.Path): |
|
175 os.makedirs(self.Path) |
|
176 self.ParentLibrary = parent_library |
|
177 |
|
178 if parent_library is not None: |
|
179 self.LoadModules() |
|
180 else: |
|
181 self.Library = None |
|
182 self.LoadModulesExtraParams() |
|
183 |
|
184 def GetPath(self): |
|
185 return self.Path |
|
186 |
|
187 def GetModulesExtraParamsFilePath(self): |
|
188 return os.path.join(self.Path, "modules_extra_params.cfg") |
|
189 |
|
190 groups_xpath = EtherCATInfo_XPath("Descriptions/Groups/Group") |
|
191 devices_xpath = EtherCATInfo_XPath("Descriptions/Devices/Device") |
|
192 def LoadModules(self): |
|
193 self.Library = {} |
|
194 |
|
195 files = os.listdir(self.Path) |
|
196 for file in files: |
|
197 filepath = os.path.join(self.Path, file) |
|
198 if os.path.isfile(filepath) and os.path.splitext(filepath)[-1] == ".xml": |
|
199 self.modules_infos = None |
|
200 |
|
201 xmlfile = open(filepath, 'r') |
|
202 try: |
|
203 self.modules_infos, error = EtherCATInfoParser.LoadXMLString(xmlfile.read()) |
|
204 if error is not None: |
|
205 self.GetCTRoot().logger.write_warning( |
|
206 XSDSchemaErrorMessage % (filepath + error)) |
|
207 except Exception, exc: |
|
208 self.modules_infos, error = None, unicode(exc) |
|
209 xmlfile.close() |
|
210 |
|
211 if self.modules_infos is not None: |
|
212 vendor = self.modules_infos.getVendor() |
|
213 |
|
214 vendor_category = self.Library.setdefault( |
|
215 ExtractHexDecValue(vendor.getId()), |
|
216 {"name": ExtractName(vendor.getName(), _("Miscellaneous")), |
|
217 "groups": {}}) |
|
218 |
|
219 for group in self.groups_xpath(self.modules_infos): |
|
220 group_type = group.getType() |
|
221 |
|
222 vendor_category["groups"].setdefault(group_type, |
|
223 {"name": ExtractName(group.getName(), group_type), |
|
224 "parent": group.getParentGroup(), |
|
225 "order": group.getSortOrder(), |
|
226 #"value": group.getcontent()["value"], |
|
227 "devices": []}) |
|
228 |
|
229 for device in self.devices_xpath(self.modules_infos): |
|
230 device_group = device.getGroupType() |
|
231 if not vendor_category["groups"].has_key(device_group): |
|
232 raise ValueError, "Not such group \"%\"" % device_group |
|
233 vendor_category["groups"][device_group]["devices"].append( |
|
234 (device.getType().getcontent(), device)) |
|
235 |
|
236 else: |
|
237 |
|
238 self.GetCTRoot().logger.write_error( |
|
239 _("Couldn't load %s XML file:\n%s") % (filepath, error)) |
|
240 |
|
241 return self.Library |
|
242 |
|
243 def GetModulesLibrary(self, profile_filter=None): |
|
244 if self.Library is None: |
|
245 self.LoadModules() |
|
246 library = [] |
|
247 for vendor_id, vendor in self.Library.iteritems(): |
|
248 groups = [] |
|
249 children_dict = {} |
|
250 for group_type, group in vendor["groups"].iteritems(): |
|
251 group_infos = {"name": group["name"], |
|
252 "order": group["order"], |
|
253 "type": ETHERCAT_GROUP, |
|
254 "infos": None, |
|
255 "children": children_dict.setdefault(group_type, [])} |
|
256 device_dict = {} |
|
257 for device_type, device in group["devices"]: |
|
258 if profile_filter is None or profile_filter in device.GetProfileNumbers(): |
|
259 product_code = device.getType().getProductCode() |
|
260 revision_number = device.getType().getRevisionNo() |
|
261 module_infos = {"device_type": device_type, |
|
262 "vendor": vendor_id, |
|
263 "product_code": product_code, |
|
264 "revision_number": revision_number} |
|
265 module_infos.update(self.GetModuleExtraParams(vendor_id, product_code, revision_number)) |
|
266 device_infos = {"name": ExtractName(device.getName()), |
|
267 "type": ETHERCAT_DEVICE, |
|
268 "infos": module_infos, |
|
269 "children": []} |
|
270 group_infos["children"].append(device_infos) |
|
271 device_type_occurrences = device_dict.setdefault(device_type, []) |
|
272 device_type_occurrences.append(device_infos) |
|
273 for device_type_occurrences in device_dict.itervalues(): |
|
274 if len(device_type_occurrences) > 1: |
|
275 for occurrence in device_type_occurrences: |
|
276 occurrence["name"] += _(" (rev. %s)") % occurrence["infos"]["revision_number"] |
|
277 if len(group_infos["children"]) > 0: |
|
278 if group["parent"] is not None: |
|
279 parent_children = children_dict.setdefault(group["parent"], []) |
|
280 parent_children.append(group_infos) |
|
281 else: |
|
282 groups.append(group_infos) |
|
283 if len(groups) > 0: |
|
284 library.append({"name": vendor["name"], |
|
285 "type": ETHERCAT_VENDOR, |
|
286 "infos": None, |
|
287 "children": groups}) |
|
288 library.sort(lambda x, y: cmp(x["name"], y["name"])) |
|
289 return library |
|
290 |
|
291 def GetVendors(self): |
|
292 return [(vendor_id, vendor["name"]) for vendor_id, vendor in self.Library.items()] |
|
293 |
|
294 def GetModuleInfos(self, module_infos): |
|
295 vendor = ExtractHexDecValue(module_infos["vendor"]) |
|
296 vendor_infos = self.Library.get(vendor) |
|
297 if vendor_infos is not None: |
|
298 for group_name, group_infos in vendor_infos["groups"].iteritems(): |
|
299 for device_type, device_infos in group_infos["devices"]: |
|
300 product_code = ExtractHexDecValue(device_infos.getType().getProductCode()) |
|
301 revision_number = ExtractHexDecValue(device_infos.getType().getRevisionNo()) |
|
302 if (product_code == ExtractHexDecValue(module_infos["product_code"]) and |
|
303 revision_number == ExtractHexDecValue(module_infos["revision_number"])): |
|
304 self.cntdevice = device_infos |
|
305 self.cntdeviceType = device_type |
|
306 return device_infos, self.GetModuleExtraParams(vendor, product_code, revision_number) |
|
307 return None, None |
|
308 |
|
309 def ImportModuleLibrary(self, filepath): |
|
310 if os.path.isfile(filepath): |
|
311 shutil.copy(filepath, self.Path) |
|
312 self.LoadModules() |
|
313 return True |
|
314 return False |
|
315 |
|
316 def LoadModulesExtraParams(self): |
|
317 self.ModulesExtraParams = {} |
|
318 |
|
319 csvfile_path = self.GetModulesExtraParamsFilePath() |
|
320 if os.path.exists(csvfile_path): |
|
321 csvfile = open(csvfile_path, "rb") |
|
322 sample = csvfile.read(1024) |
|
323 csvfile.seek(0) |
|
324 dialect = csv.Sniffer().sniff(sample) |
|
325 has_header = csv.Sniffer().has_header(sample) |
|
326 reader = csv.reader(csvfile, dialect) |
|
327 for row in reader: |
|
328 if has_header: |
|
329 has_header = False |
|
330 else: |
|
331 params_values = {} |
|
332 for (param, param_infos), value in zip( |
|
333 self.MODULES_EXTRA_PARAMS, row[3:]): |
|
334 if value != "": |
|
335 params_values[param] = int(value) |
|
336 self.ModulesExtraParams[ |
|
337 tuple(map(int, row[:3]))] = params_values |
|
338 csvfile.close() |
|
339 |
|
340 def SaveModulesExtraParams(self): |
|
341 csvfile = open(self.GetModulesExtraParamsFilePath(), "wb") |
|
342 extra_params = [param for param, params_infos in self.MODULES_EXTRA_PARAMS] |
|
343 writer = csv.writer(csvfile, delimiter=';') |
|
344 writer.writerow(['Vendor', 'product_code', 'revision_number'] + extra_params) |
|
345 for (vendor, product_code, revision_number), module_extra_params in self.ModulesExtraParams.iteritems(): |
|
346 writer.writerow([vendor, product_code, revision_number] + |
|
347 [module_extra_params.get(param, '') |
|
348 for param in extra_params]) |
|
349 csvfile.close() |
|
350 |
|
351 def SetModuleExtraParam(self, vendor, product_code, revision_number, param, value): |
|
352 vendor = ExtractHexDecValue(vendor) |
|
353 product_code = ExtractHexDecValue(product_code) |
|
354 revision_number = ExtractHexDecValue(revision_number) |
|
355 |
|
356 module_infos = (vendor, product_code, revision_number) |
|
357 self.ModulesExtraParams.setdefault(module_infos, {}) |
|
358 self.ModulesExtraParams[module_infos][param] = value |
|
359 |
|
360 self.SaveModulesExtraParams() |
|
361 |
|
362 def GetModuleExtraParams(self, vendor, product_code, revision_number): |
|
363 vendor = ExtractHexDecValue(vendor) |
|
364 product_code = ExtractHexDecValue(product_code) |
|
365 revision_number = ExtractHexDecValue(revision_number) |
|
366 |
|
367 if self.ParentLibrary is not None: |
|
368 extra_params = self.ParentLibrary.GetModuleExtraParams(vendor, product_code, revision_number) |
|
369 else: |
|
370 extra_params = {} |
|
371 |
|
372 extra_params.update(self.ModulesExtraParams.get((vendor, product_code, revision_number), {})) |
|
373 |
|
374 for param, param_infos in self.MODULES_EXTRA_PARAMS: |
|
375 extra_params.setdefault(param, param_infos["default"]) |
|
376 |
|
377 return extra_params |
|
378 |
|
379 USERDATA_DIR = wx.StandardPaths.Get().GetUserDataDir() |
|
380 if wx.Platform != '__WXMSW__': |
|
381 USERDATA_DIR += '_files' |
|
382 |
|
383 ModulesDatabase = ModulesLibrary( |
|
384 os.path.join(USERDATA_DIR, "ethercat_modules")) |
|
385 |
|
386 class RootClass: |
|
387 |
|
388 CTNChildrenTypes = [("EthercatNode",_EthercatCTN,"Ethercat Master")] |
|
389 EditorType = LibraryEditor |
|
390 |
|
391 |
|
392 def __init__(self): |
|
393 self.ModulesLibrary = None |
|
394 self.LoadModulesLibrary() |
|
395 |
|
396 def GetIconName(self): |
|
397 return "Ethercat" |
|
398 |
|
399 def GetModulesLibraryPath(self, project_path=None): |
|
400 if project_path is None: |
|
401 project_path = self.CTNPath() |
|
402 return os.path.join(project_path, "modules") |
|
403 |
|
404 def OnCTNSave(self, from_project_path=None): |
|
405 if from_project_path is not None: |
|
406 shutil.copytree(self.GetModulesLibraryPath(from_project_path), |
|
407 self.GetModulesLibraryPath()) |
|
408 return True |
|
409 |
|
410 def CTNGenerate_C(self, buildpath, locations): |
|
411 return [],"",False |
|
412 |
|
413 def LoadModulesLibrary(self): |
|
414 if self.ModulesLibrary is None: |
|
415 self.ModulesLibrary = ModulesLibrary(self.GetModulesLibraryPath(), ModulesDatabase) |
|
416 else: |
|
417 self.ModulesLibrary.LoadModulesLibrary() |
|
418 |
|
419 def GetModulesDatabaseInstance(self): |
|
420 return ModulesDatabase |
|
421 |
|
422 def GetModulesLibraryInstance(self): |
|
423 return self.ModulesLibrary |
|
424 |
|
425 def GetModulesLibrary(self, profile_filter=None): |
|
426 return self.ModulesLibrary.GetModulesLibrary(profile_filter) |
|
427 |
|
428 def GetVendors(self): |
|
429 return self.ModulesLibrary.GetVendors() |
|
430 |
|
431 def GetModuleInfos(self, module_infos): |
|
432 return self.ModulesLibrary.GetModuleInfos(module_infos) |
|
433 |