laurent@2022: import os, shutil laurent@2022: from xml.dom import minidom laurent@2022: laurent@2022: import wx Laurent@2097: import csv laurent@2022: laurent@2022: from xmlclass import * Laurent@2111: Edouard@2048: from ConfigTreeNode import ConfigTreeNode Edouard@2048: from PLCControler import UndoBuffer, LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY Laurent@2111: Laurent@2111: from EthercatSlave import ExtractHexDecValue, ExtractName Laurent@2111: from EthercatMaster import _EthercatCTN Laurent@2111: from ConfigEditor import LibraryEditor, ETHERCAT_VENDOR, ETHERCAT_GROUP, ETHERCAT_DEVICE laurent@2022: laurent@2022: #-------------------------------------------------- Edouard@2048: # Ethercat ConfNode laurent@2022: #-------------------------------------------------- laurent@2022: laurent@2022: EtherCATInfoClasses = GenerateClassesFromXSD(os.path.join(os.path.dirname(__file__), "EtherCATInfo.xsd")) laurent@2022: Laurent@2074: cls = EtherCATInfoClasses["EtherCATBase.xsd"].get("DictionaryType", None) Laurent@2074: if cls: Laurent@2074: cls.loadXMLTreeArgs = None Laurent@2074: Laurent@2074: setattr(cls, "_loadXMLTree", getattr(cls, "loadXMLTree")) Laurent@2074: Laurent@2074: def loadXMLTree(self, *args): Laurent@2074: self.loadXMLTreeArgs = args Laurent@2074: setattr(cls, "loadXMLTree", loadXMLTree) Laurent@2074: Laurent@2074: def load(self): Laurent@2074: if self.loadXMLTreeArgs is not None: Laurent@2074: self._loadXMLTree(*self.loadXMLTreeArgs) Laurent@2074: self.loadXMLTreeArgs = None Laurent@2074: setattr(cls, "load", load) Laurent@2074: laurent@2029: cls = EtherCATInfoClasses["EtherCATInfo.xsd"].get("DeviceType", None) laurent@2029: if cls: laurent@2029: cls.DataTypes = None laurent@2029: laurent@2032: def GetProfileNumbers(self): laurent@2032: profiles = [] laurent@2032: laurent@2032: for profile in self.getProfile(): laurent@2032: profile_content = profile.getcontent() laurent@2032: if profile_content is None: laurent@2032: continue laurent@2032: laurent@2032: for content_element in profile_content["value"]: laurent@2032: if content_element["name"] == "ProfileNo": laurent@2032: profiles.append(content_element["value"]) laurent@2032: laurent@2032: return profiles laurent@2032: setattr(cls, "GetProfileNumbers", GetProfileNumbers) laurent@2032: laurent@2029: def GetProfileDictionaries(self): laurent@2029: dictionaries = [] laurent@2029: laurent@2029: for profile in self.getProfile(): laurent@2029: laurent@2029: profile_content = profile.getcontent() laurent@2029: if profile_content is None: laurent@2031: continue laurent@2029: laurent@2029: for content_element in profile_content["value"]: laurent@2029: if content_element["name"] == "Dictionary": laurent@2029: dictionaries.append(content_element["value"]) laurent@2029: elif content_element["name"] == "DictionaryFile": laurent@2029: raise ValueError, "DictionaryFile for defining Device Profile is not yet supported!" laurent@2029: laurent@2029: return dictionaries laurent@2029: setattr(cls, "GetProfileDictionaries", GetProfileDictionaries) laurent@2029: laurent@2029: def ExtractDataTypes(self): laurent@2029: self.DataTypes = {} laurent@2029: laurent@2029: for dictionary in self.GetProfileDictionaries(): Laurent@2098: dictionary.load() laurent@2029: laurent@2029: datatypes = dictionary.getDataTypes() laurent@2029: if datatypes is not None: laurent@2029: laurent@2029: for datatype in datatypes.getDataType(): laurent@2029: content = datatype.getcontent() laurent@2029: if content is not None and content["name"] == "SubItem": laurent@2029: self.DataTypes[datatype.getName()] = datatype laurent@2029: laurent@2029: setattr(cls, "ExtractDataTypes", ExtractDataTypes) laurent@2029: Laurent@2079: def getCoE(self): laurent@2032: mailbox = self.getMailbox() Laurent@2079: if mailbox is not None: Laurent@2079: return mailbox.getCoE() Laurent@2079: return None Laurent@2079: setattr(cls, "getCoE", getCoE) laurent@2032: Laurent@2098: def GetEntriesList(self, limits=None): laurent@2029: if self.DataTypes is None: laurent@2029: self.ExtractDataTypes() laurent@2029: laurent@2029: entries = {} laurent@2029: laurent@2029: for dictionary in self.GetProfileDictionaries(): Laurent@2074: dictionary.load() laurent@2029: laurent@2029: for object in dictionary.getObjects().getObject(): laurent@2029: entry_index = object.getIndex().getcontent() laurent@2029: index = ExtractHexDecValue(entry_index) Laurent@2098: if limits is None or limits[0] <= index <= limits[1]: Laurent@2098: entry_type = object.getType() Laurent@2098: entry_name = ExtractName(object.getName()) Laurent@2098: Laurent@2098: entry_type_infos = self.DataTypes.get(entry_type, None) Laurent@2098: if entry_type_infos is not None: Laurent@2098: content = entry_type_infos.getcontent() Laurent@2098: for subitem in content["value"]: Laurent@2098: entry_subidx = subitem.getSubIdx() Laurent@2098: if entry_subidx is None: Laurent@2098: entry_subidx = "0" Laurent@2098: subidx = ExtractHexDecValue(entry_subidx) Laurent@2098: subitem_access = "" Laurent@2098: subitem_pdomapping = "" Laurent@2098: subitem_flags = subitem.getFlags() Laurent@2098: if subitem_flags is not None: Laurent@2098: access = subitem_flags.getAccess() Laurent@2098: if access is not None: Laurent@2098: subitem_access = access.getcontent() Laurent@2098: pdomapping = subitem_flags.getPdoMapping() Laurent@2098: if pdomapping is not None: Laurent@2098: subitem_pdomapping = pdomapping.upper() Laurent@2098: entries[(index, subidx)] = { Laurent@2098: "Index": entry_index, Laurent@2098: "SubIndex": entry_subidx, Laurent@2098: "Name": "%s - %s" % Laurent@2098: (entry_name.decode("utf-8"), Laurent@2098: ExtractName(subitem.getDisplayName(), Laurent@2098: subitem.getName()).decode("utf-8")), Laurent@2098: "Type": subitem.getType(), Laurent@2098: "BitSize": subitem.getBitSize(), Laurent@2098: "Access": subitem_access, Laurent@2098: "PDOMapping": subitem_pdomapping} Laurent@2098: else: Laurent@2098: entry_access = "" Laurent@2098: entry_pdomapping = "" Laurent@2098: entry_flags = object.getFlags() Laurent@2098: if entry_flags is not None: Laurent@2098: access = entry_flags.getAccess() laurent@2029: if access is not None: Laurent@2098: entry_access = access.getcontent() Laurent@2098: pdomapping = entry_flags.getPdoMapping() laurent@2032: if pdomapping is not None: Laurent@2098: entry_pdomapping = pdomapping.upper() Laurent@2098: entries[(index, 0)] = { Laurent@2098: "Index": entry_index, Laurent@2098: "SubIndex": "0", Laurent@2098: "Name": entry_name, Laurent@2098: "Type": entry_type, Laurent@2098: "BitSize": object.getBitSize(), Laurent@2098: "Access": entry_access, Laurent@2098: "PDOMapping": entry_pdomapping} laurent@2029: laurent@2029: for TxPdo in self.getTxPdo(): Laurent@2098: ExtractPdoInfos(TxPdo, "Transmit", entries, limits) laurent@2029: for RxPdo in self.getRxPdo(): Laurent@2098: ExtractPdoInfos(RxPdo, "Receive", entries, limits) laurent@2029: laurent@2029: return entries laurent@2029: setattr(cls, "GetEntriesList", GetEntriesList) laurent@2029: laurent@2029: def GetSyncManagers(self): laurent@2029: sync_managers = [] laurent@2029: for sync_manager in self.getSm(): laurent@2029: sync_manager_infos = {} laurent@2029: for name, value in [("Name", sync_manager.getcontent()), laurent@2029: ("Start Address", sync_manager.getStartAddress()), laurent@2029: ("Default Size", sync_manager.getDefaultSize()), laurent@2029: ("Control Byte", sync_manager.getControlByte()), laurent@2029: ("Enable", sync_manager.getEnable())]: laurent@2029: if value is None: laurent@2029: value ="" laurent@2029: sync_manager_infos[name] = value laurent@2029: sync_managers.append(sync_manager_infos) laurent@2029: return sync_managers laurent@2029: setattr(cls, "GetSyncManagers", GetSyncManagers) laurent@2029: laurent@2022: def GroupItemCompare(x, y): laurent@2022: if x["type"] == y["type"]: laurent@2022: if x["type"] == ETHERCAT_GROUP: laurent@2031: return cmp(x["order"], y["order"]) laurent@2022: else: laurent@2031: return cmp(x["name"], y["name"]) laurent@2022: elif x["type"] == ETHERCAT_GROUP: laurent@2022: return -1 laurent@2022: return 1 laurent@2022: laurent@2022: def SortGroupItems(group): laurent@2022: for item in group["children"]: laurent@2022: if item["type"] == ETHERCAT_GROUP: laurent@2022: SortGroupItems(item) laurent@2022: group["children"].sort(GroupItemCompare) laurent@2022: Laurent@2098: def ExtractPdoInfos(pdo, pdo_type, entries, limits=None): laurent@2022: pdo_index = pdo.getIndex().getcontent() laurent@2029: pdo_name = ExtractName(pdo.getName()) laurent@2029: for pdo_entry in pdo.getEntry(): laurent@2029: entry_index = pdo_entry.getIndex().getcontent() laurent@2029: entry_subindex = pdo_entry.getSubIndex() laurent@2029: index = ExtractHexDecValue(entry_index) laurent@2029: subindex = ExtractHexDecValue(entry_subindex) laurent@2029: Laurent@2098: if limits is None or limits[0] <= index <= limits[1]: Laurent@2098: entry = entries.get((index, subindex), None) Laurent@2098: if entry is not None: Laurent@2098: entry["PDO index"] = pdo_index Laurent@2098: entry["PDO name"] = pdo_name Laurent@2098: entry["PDO type"] = pdo_type Laurent@2098: else: Laurent@2098: entry_type = pdo_entry.getDataType() Laurent@2098: if entry_type is not None: Laurent@2098: if pdo_type == "Transmit": Laurent@2098: access = "ro" Laurent@2098: pdomapping = "T" Laurent@2098: else: Laurent@2098: access = "wo" Laurent@2098: pdomapping = "R" Laurent@2098: entries[(index, subindex)] = { Laurent@2098: "Index": entry_index, Laurent@2098: "SubIndex": entry_subindex, Laurent@2098: "Name": ExtractName(pdo_entry.getName()), Laurent@2098: "Type": entry_type.getcontent(), Laurent@2098: "Access": access, Laurent@2098: "PDOMapping": pdomapping} Laurent@2097: Laurent@2097: class ModulesLibrary: Laurent@2137: Laurent@2137: MODULES_EXTRA_PARAMS = [ Laurent@2137: ("pdo_alignment", { Laurent@2137: "column_label": _("PDO alignment (bits)"), Laurent@2137: "default": 8}), Laurent@2137: ("max_pdo_size", { Laurent@2137: "column_label": _("Max entries by PDO (-1=unbounded)"), Laurent@2137: "default": -1}), Laurent@2137: ("add_pdo", { Laurent@2137: "column_label": _("Creating new PDO (1=possible)"), Laurent@2137: "default": 0}) Laurent@2137: ] Laurent@2097: Laurent@2097: def __init__(self, path, parent_library=None): Laurent@2097: self.Path = path Laurent@2097: if not os.path.exists(self.Path): Laurent@2097: os.makedirs(self.Path) Laurent@2097: self.ParentLibrary = parent_library Laurent@2097: Laurent@2098: if parent_library is not None: Laurent@2098: self.LoadModules() Laurent@2098: else: Laurent@2098: self.Library = None Laurent@2137: self.LoadModulesExtraParams() Laurent@2097: Laurent@2097: def GetPath(self): Laurent@2097: return self.Path Laurent@2097: Laurent@2137: def GetModulesExtraParamsFilePath(self): Laurent@2137: return os.path.join(self.Path, "modules_extra_params.cfg") Laurent@2097: Laurent@2097: def LoadModules(self): Laurent@2097: self.Library = {} Laurent@2097: Laurent@2097: files = os.listdir(self.Path) laurent@2022: for file in files: Laurent@2097: filepath = os.path.join(self.Path, file) laurent@2022: if os.path.isfile(filepath) and os.path.splitext(filepath)[-1] == ".xml": laurent@2022: xmlfile = open(filepath, 'r') laurent@2022: xml_tree = minidom.parse(xmlfile) laurent@2022: xmlfile.close() laurent@2022: laurent@2022: modules_infos = None laurent@2022: for child in xml_tree.childNodes: laurent@2022: if child.nodeType == xml_tree.ELEMENT_NODE and child.nodeName == "EtherCATInfo": laurent@2022: modules_infos = EtherCATInfoClasses["EtherCATInfo.xsd"]["EtherCATInfo"]() laurent@2051: modules_infos.loadXMLTree(child) laurent@2022: laurent@2022: if modules_infos is not None: laurent@2022: vendor = modules_infos.getVendor() laurent@2022: Laurent@2097: vendor_category = self.Library.setdefault(ExtractHexDecValue(vendor.getId()), Laurent@2097: {"name": ExtractName(vendor.getName(), _("Miscellaneous")), Laurent@2097: "groups": {}}) laurent@2022: laurent@2022: for group in modules_infos.getDescriptions().getGroups().getGroup(): laurent@2022: group_type = group.getType() laurent@2022: laurent@2022: vendor_category["groups"].setdefault(group_type, {"name": ExtractName(group.getName(), group_type), laurent@2022: "parent": group.getParentGroup(), laurent@2022: "order": group.getSortOrder(), laurent@2022: "devices": []}) laurent@2022: laurent@2022: for device in modules_infos.getDescriptions().getDevices().getDevice(): laurent@2022: device_group = device.getGroupType() laurent@2022: if not vendor_category["groups"].has_key(device_group): laurent@2022: raise ValueError, "Not such group \"%\"" % device_group laurent@2022: vendor_category["groups"][device_group]["devices"].append((device.getType().getcontent(), device)) Laurent@2097: laurent@2041: def GetModulesLibrary(self, profile_filter=None): Laurent@2098: if self.Library is None: Laurent@2098: self.LoadModules() laurent@2022: library = [] Laurent@2097: for vendor_id, vendor in self.Library.iteritems(): laurent@2022: groups = [] Laurent@2073: children_dict = {} laurent@2022: for group_type, group in vendor["groups"].iteritems(): laurent@2022: group_infos = {"name": group["name"], laurent@2022: "order": group["order"], laurent@2022: "type": ETHERCAT_GROUP, laurent@2041: "infos": None, laurent@2022: "children": children_dict.setdefault(group_type, [])} laurent@2022: device_dict = {} laurent@2022: for device_type, device in group["devices"]: laurent@2041: if profile_filter is None or profile_filter in device.GetProfileNumbers(): Laurent@2097: product_code = device.getType().getProductCode() Laurent@2097: revision_number = device.getType().getRevisionNo() Laurent@2137: module_infos = {"device_type": device_type, Laurent@2137: "vendor": vendor_id, Laurent@2137: "product_code": product_code, Laurent@2137: "revision_number": revision_number} Laurent@2137: module_infos.update(self.GetModuleExtraParams(vendor_id, product_code, revision_number)) laurent@2041: device_infos = {"name": ExtractName(device.getName()), laurent@2041: "type": ETHERCAT_DEVICE, Laurent@2137: "infos": module_infos, laurent@2041: "children": []} laurent@2041: group_infos["children"].append(device_infos) laurent@2041: device_type_occurrences = device_dict.setdefault(device_type, []) laurent@2041: device_type_occurrences.append(device_infos) laurent@2022: for device_type_occurrences in device_dict.itervalues(): laurent@2022: if len(device_type_occurrences) > 1: laurent@2022: for occurrence in device_type_occurrences: laurent@2022: occurrence["name"] += _(" (rev. %s)") % occurrence["infos"]["revision_number"] laurent@2041: if len(group_infos["children"]) > 0: laurent@2041: if group["parent"] is not None: laurent@2041: parent_children = children_dict.setdefault(group["parent"], []) laurent@2041: parent_children.append(group_infos) laurent@2041: else: laurent@2041: groups.append(group_infos) laurent@2041: if len(groups) > 0: laurent@2041: library.append({"name": vendor["name"], laurent@2041: "type": ETHERCAT_VENDOR, laurent@2041: "infos": None, laurent@2041: "children": groups}) laurent@2031: library.sort(lambda x, y: cmp(x["name"], y["name"])) laurent@2022: return library Laurent@2097: Laurent@2098: def GetVendors(self): Laurent@2098: return [(vendor_id, vendor["name"]) for vendor_id, vendor in self.Library.items()] Laurent@2098: Laurent@2097: def GetModuleInfos(self, module_infos): Laurent@2097: vendor = ExtractHexDecValue(module_infos["vendor"]) Laurent@2097: vendor_infos = self.Library.get(vendor) Laurent@2097: if vendor_infos is not None: Laurent@2097: for group_name, group_infos in vendor_infos["groups"].iteritems(): Laurent@2097: for device_type, device_infos in group_infos["devices"]: Laurent@2097: product_code = ExtractHexDecValue(device_infos.getType().getProductCode()) Laurent@2097: revision_number = ExtractHexDecValue(device_infos.getType().getRevisionNo()) Laurent@2097: if (product_code == ExtractHexDecValue(module_infos["product_code"]) and Laurent@2097: revision_number == ExtractHexDecValue(module_infos["revision_number"])): Laurent@2137: return device_infos, self.GetModuleExtraParams(vendor, product_code, revision_number) Laurent@2097: return None, None Laurent@2097: Laurent@2097: def ImportModuleLibrary(self, filepath): Laurent@2097: if os.path.isfile(filepath): Laurent@2097: shutil.copy(filepath, self.Path) Laurent@2097: self.LoadModules() Laurent@2097: return True Laurent@2097: return False Laurent@2097: Laurent@2137: def LoadModulesExtraParams(self): Laurent@2137: self.ModulesExtraParams = {} Laurent@2137: Laurent@2137: csvfile_path = self.GetModulesExtraParamsFilePath() Laurent@2097: if os.path.exists(csvfile_path): Laurent@2097: csvfile = open(csvfile_path, "rb") Laurent@2097: sample = csvfile.read(1024) Laurent@2097: csvfile.seek(0) Laurent@2097: dialect = csv.Sniffer().sniff(sample) Laurent@2097: has_header = csv.Sniffer().has_header(sample) Laurent@2097: reader = csv.reader(csvfile, dialect) Laurent@2097: for row in reader: Laurent@2097: if has_header: Laurent@2097: has_header = False Laurent@2097: else: Laurent@2097: try: Laurent@2137: self.ModulesExtraParams[tuple(map(int, row[:3]))] = dict( Laurent@2137: zip([param for param, params_infos in self.MODULES_EXTRA_PARAMS], Laurent@2137: int(row[3]))) Laurent@2097: except: Laurent@2097: pass Laurent@2097: csvfile.close() Laurent@2137: Laurent@2137: def SaveModulesExtraParams(self): Laurent@2137: csvfile = open(self.GetModulesExtraParamsFilePath(), "wb") Laurent@2137: extra_params = [param for param, params_infos in self.MODULES_EXTRA_PARAMS] Laurent@2097: writer = csv.writer(csvfile, delimiter=';') Laurent@2137: writer.writerow(['Vendor', 'product_code', 'revision_number'] + extra_params) Laurent@2137: for (vendor, product_code, revision_number), module_extra_params in self.ModulesExtraParams.iteritems(): Laurent@2137: writer.writerow([vendor, product_code, revision_number] + Laurent@2137: [module_extra_params.get(param, '') Laurent@2137: for param in extra_params]) Laurent@2097: csvfile.close() Laurent@2097: Laurent@2137: def SetModuleExtraParam(self, vendor, product_code, revision_number, param, value): Laurent@2097: vendor = ExtractHexDecValue(vendor) Laurent@2097: product_code = ExtractHexDecValue(product_code) Laurent@2097: revision_number = ExtractHexDecValue(revision_number) Laurent@2097: Laurent@2137: self.ModulesExtraParams[tuple([vendor, product_code, revision_number])][param] = value Laurent@2137: self.SaveModulesExtraParams() Laurent@2137: Laurent@2137: def GetModuleExtraParams(self, vendor, product_code, revision_number): Laurent@2097: vendor = ExtractHexDecValue(vendor) Laurent@2097: product_code = ExtractHexDecValue(product_code) Laurent@2097: revision_number = ExtractHexDecValue(revision_number) Laurent@2097: Laurent@2097: if self.ParentLibrary is not None: Laurent@2137: extra_params = self.ParentLibrary.GetModuleExtraParams(vendor, product_code, revision_number) Laurent@2137: else: Laurent@2137: extra_params = {} Laurent@2137: Laurent@2137: extra_params.update(self.ModulesExtraParams.get(tuple([vendor, product_code, revision_number]), {})) Laurent@2137: Laurent@2137: for param, param_infos in self.MODULES_EXTRA_PARAMS: Laurent@2137: extra_params.setdefault(param, param_infos["default"]) Laurent@2137: Laurent@2137: return extra_params Laurent@2097: Laurent@2097: USERDATA_DIR = wx.StandardPaths.Get().GetUserDataDir() Laurent@2097: if wx.Platform != '__WXMSW__': Laurent@2097: USERDATA_DIR += '_files' Laurent@2097: Laurent@2097: ModulesDatabase = ModulesLibrary( Laurent@2097: os.path.join(USERDATA_DIR, "ethercat_modules")) Laurent@2097: Laurent@2097: class RootClass: Laurent@2097: Laurent@2097: CTNChildrenTypes = [("EthercatNode",_EthercatCTN,"Ethercat Master")] Laurent@2097: EditorType = LibraryEditor Laurent@2097: Laurent@2097: def __init__(self): Laurent@2097: self.ModulesLibrary = None Laurent@2097: self.LoadModulesLibrary() Laurent@2097: Laurent@2133: def GetModulesLibraryPath(self, project_path=None): Laurent@2133: if project_path is None: Laurent@2133: project_path = self.CTNPath() Laurent@2133: return os.path.join(project_path, "modules") Laurent@2133: Laurent@2133: def OnCTNSave(self, from_project_path=None): Laurent@2133: if from_project_path is not None: Laurent@2133: shutil.copytree(self.GetModulesLibraryPath(from_project_path), Laurent@2133: self.GetModulesLibraryPath()) Laurent@2133: return True Laurent@2097: Laurent@2097: def CTNGenerate_C(self, buildpath, locations): Laurent@2097: return [],"",False Laurent@2097: Laurent@2097: def LoadModulesLibrary(self): Laurent@2097: if self.ModulesLibrary is None: Laurent@2097: self.ModulesLibrary = ModulesLibrary(self.GetModulesLibraryPath(), ModulesDatabase) Laurent@2097: else: Laurent@2097: self.ModulesLibrary.LoadModulesLibrary() Laurent@2097: Laurent@2097: def GetModulesDatabaseInstance(self): Laurent@2097: return ModulesDatabase Laurent@2097: Laurent@2097: def GetModulesLibraryInstance(self): Laurent@2097: return self.ModulesLibrary Laurent@2097: Laurent@2097: def GetModulesLibrary(self, profile_filter=None): Laurent@2097: return self.ModulesLibrary.GetModulesLibrary(profile_filter) Laurent@2097: Laurent@2098: def GetVendors(self): Laurent@2098: return self.ModulesLibrary.GetVendors() Laurent@2098: Laurent@2097: def GetModuleInfos(self, module_infos): Laurent@2097: return self.ModulesLibrary.GetModuleInfos(module_infos) laurent@2041: laurent@2041: