etherlab/etherlab.py
changeset 2165 02a2b5dee5e3
parent 2162 43ab74687f45
child 2355 fec77f2b9e07
child 2641 c9deff128c37
equal deleted inserted replaced
2021:bcf346f558bd 2165:02a2b5dee5e3
       
     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