objdictgen/eds_utils.py
author fbeaulier
Tue, 16 Aug 2011 14:15:52 +0200
changeset 663 70fc3603e36f
parent 580 2ae92a99ac10
child 692 6818cc935ab1
permissions -rw-r--r--
timers_unix.c : remove sigint and sigterm catch
sdo : Allow multiple servers
The sdo transfer struct is not anymore referenced by server's node id but by
client or server number in the OD. Node id is not relevant in SDO transfert.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#This file is part of CanFestival, a library implementing CanOpen Stack. 
#
#Copyright (C): Edouard TISSERANT, Francis DUPIN and Laurent BESSARD
#
#See COPYING file for copyrights details.
#
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU Lesser General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#Lesser General Public License for more details.
#
#You should have received a copy of the GNU Lesser General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


import node
from node import nosub, var, array, rec, plurivar, pluriarray, plurirec
try:
    set
except NameError:
    from sets import Set as set
from types import *
from time import *
import os,re

# Regular expression for finding index section names
index_model = re.compile('([0-9A-F]{1,4}$)')
# Regular expression for finding subindex section names
subindex_model = re.compile('([0-9A-F]{1,4})SUB([0-9A-F]{1,2}$)')
# Regular expression for finding index section names
index_objectlinks_model = re.compile('([0-9A-F]{1,4}OBJECTLINKS$)')

# Regular expression for finding NodeXPresent keynames
nodepresent_model = re.compile('NODE([0-9]{1,3})PRESENT$')
# Regular expression for finding NodeXName keynames
nodename_model = re.compile('NODE([0-9]{1,3})NAME$')
# Regular expression for finding NodeXDCFName keynames
nodedcfname_model = re.compile('NODE([0-9]{1,3})DCFNAME$')

# Dictionary for quickly translate boolean into integer value
BOOL_TRANSLATE = {True : "1", False : "0"}

# Dictionary for quickly translate eds access value into canfestival access value
ACCESS_TRANSLATE = {"RO" : "ro", "WO" : "wo", "RW" : "rw", "RWR" : "rw", "RWW" : "rw", "CONST" : "ro"}

# Function for verifying data values
is_integer = lambda x: type(x) in (IntType, LongType)
is_string = lambda x: type(x) in (StringType, UnicodeType)
is_boolean = lambda x: x in (0, 1)

# Define checking of value for each attribute
ENTRY_ATTRIBUTES = {"SUBNUMBER" : is_integer, 
                    "PARAMETERNAME" : is_string, 
                    "OBJECTTYPE" : lambda x: x in (2, 7, 8, 9), 
                    "DATATYPE" : is_integer, 
                    "LOWLIMIT" : is_integer, 
                    "HIGHLIMIT" : is_integer,
                    "ACCESSTYPE" : lambda x: x.upper() in ACCESS_TRANSLATE.keys(),
                    "DEFAULTVALUE" : lambda x: True, 
                    "PDOMAPPING" : is_boolean,
                    "OBJFLAGS" : is_integer, 
                    "PARAMETERVALUE" : lambda x: True,
                    "UPLOADFILE" : is_string,
                    "DOWNLOADFILE" : is_string}

# Define entry parameters by entry ObjectType number
ENTRY_TYPES = {2 : {"name" : " DOMAIN",
                    "require" : ["PARAMETERNAME", "OBJECTTYPE"],
                    "optional" : ["DATATYPE", "ACCESSTYPE", "DEFAULTVALUE", "OBJFLAGS"]},
               7 : {"name" : " VAR",
                    "require" : ["PARAMETERNAME", "DATATYPE", "ACCESSTYPE"],
                    "optional" : ["OBJECTTYPE", "DEFAULTVALUE", "PDOMAPPING", "LOWLIMIT", "HIGHLIMIT", "OBJFLAGS", "PARAMETERVALUE"]},
               8 : {"name" : "n ARRAY",
                    "require" : ["PARAMETERNAME", "OBJECTTYPE", "SUBNUMBER"],
                    "optional" : ["OBJFLAGS"]},
               9 : {"name" : " RECORD",
                    "require" : ["PARAMETERNAME", "OBJECTTYPE", "SUBNUMBER"],
                    "optional" : ["OBJFLAGS"]}}


# Function that search into Node Mappings the informations about an index or a subindex
# and return the default value
def GetDefaultValue(Node, index, subIndex = None):
    infos = Node.GetEntryInfos(index)
    if infos["struct"] & node.OD_MultipleSubindexes:
        # First case entry is a record
        if infos["struct"] & node.OD_IdenticalSubindexes:
            subentry_infos = Node.GetSubentryInfos(index, 1)
        # Second case entry is an array
        else:
            subentry_infos = Node.GetSubentryInfos(index, subIndex)
        # If a default value is defined for this subindex, returns it
        if "default" in subentry_infos:
            return subentry_infos["default"]
        # If not, returns the default value for the subindex type
        else:
            return Node.GetTypeDefaultValue(subentry_infos["type"])
    # Third case entry is a var
    else:
        subentry_infos = Node.GetSubentryInfos(index, 0)
        # If a default value is defined for this subindex, returns it
        if "default" in subentry_infos:
            return subentry_infos["default"]
        # If not, returns the default value for the subindex type
        else:
            return Node.GetTypeDefaultValue(subentry_infos["type"])
    return None


#-------------------------------------------------------------------------------
#                               Parse file
#-------------------------------------------------------------------------------


# List of section names that are not index and subindex and that we can meet in
# an EDS file
SECTION_KEYNAMES = ["FILEINFO", "DEVICEINFO", "DUMMYUSAGE", "COMMENTS", 
                    "MANDATORYOBJECTS", "OPTIONALOBJECTS", "MANUFACTUREROBJECTS",
                    "STANDARDDATATYPES", "SUPPORTEDMODULES"]


# Function that extract sections from a file and returns a dictionary of the informations
def ExtractSections(file):
    return [(blocktuple[0],                # EntryName : Assignements dict
             blocktuple[-1].splitlines())  # all the lines
             for blocktuple in [           # Split the eds files into
             block.split("]", 1)              # (EntryName,Assignements) tuple
             for block in                  # for each blocks staring with '['
             ("\n"+file).split("\n[")]
             if blocktuple[0].isalnum()]   # if EntryName exists
    

# Function that parse an CPJ file and returns a dictionary of the informations
def ParseCPJFile(filepath):
    networks = []
    # Read file text
    cpj_file = open(filepath,'r').read()
    sections = ExtractSections(cpj_file)
    # Parse assignments for each section
    for section_name, assignments in sections:
        
        # Verify that section name is TOPOLOGY 
        if section_name.upper() in "TOPOLOGY":
            
            # Reset values for topology
            topology = {"Name" : "", "Nodes" : {}}
            
            for assignment in assignments:
                # Escape any comment
                if assignment.startswith(";"):
                    pass
                # Verify that line is a valid assignment
                elif assignment.find('=') > 0:
                    # Split assignment into the two values keyname and value
                    keyname, value = assignment.split("=", 1)
                    
                    # keyname must be immediately followed by the "=" sign, so we
                    # verify that there is no whitespace into keyname
                    if keyname.isalnum():
                        # value can be preceded and followed by whitespaces, so we escape them
                        value = value.strip()
                
                        # First case, value starts with "0x" or "-0x", then it's an hexadecimal value
                        if value.startswith("0x") or value.startswith("-0x"):
                            try:
                                computed_value = int(value, 16)
                            except:
                                raise SyntaxError, _("\"%s\" is not a valid value for attribute \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                        elif value.isdigit() or value.startswith("-") and value[1:].isdigit():
                            # Second case, value is a number and starts with "0" or "-0", then it's an octal value
                            if value.startswith("0") or value.startswith("-0"):
                                computed_value = int(value, 8)
                            # Third case, value is a number and don't start with "0", then it's a decimal value
                            else:
                                computed_value = int(value)
                        # In any other case, we keep string value
                        else:
                            computed_value = value
                        
                        # Search if the section name match any cpj expression
                        nodepresent_result = nodepresent_model.match(keyname.upper())
                        nodename_result = nodename_model.match(keyname.upper())
                        nodedcfname_result = nodedcfname_model.match(keyname.upper())
                        
                        if keyname.upper() == "NETNAME":
                            if not is_string(computed_value):
                                raise SyntaxError, _("Invalid value \"%s\" for keyname \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                            topology["Name"] = computed_value
                        elif keyname.upper() == "NODES":
                            if not is_integer(computed_value):
                                raise SyntaxError, _("Invalid value \"%s\" for keyname \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                            topology["Number"] = computed_value
                        elif keyname.upper() == "EDSBASENAME":
                            if not is_string(computed_value):
                                raise SyntaxError, _("Invalid value \"%s\" for keyname \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                            topology["Path"] = computed_value
                        elif nodepresent_result:
                            if not is_boolean(computed_value):
                                raise SyntaxError, _("Invalid value \"%s\" for keyname \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                            nodeid = int(nodepresent_result.groups()[0])
                            if nodeid not in topology["Nodes"].keys():
                                topology["Nodes"][nodeid] = {}
                            topology["Nodes"][nodeid]["Present"] = computed_value
                        elif nodename_result:
                            if not is_string(value):
                                raise SyntaxError, _("Invalid value \"%s\" for keyname \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                            nodeid = int(nodename_result.groups()[0])
                            if nodeid not in topology["Nodes"].keys():
                                topology["Nodes"][nodeid] = {}
                            topology["Nodes"][nodeid]["Name"] = computed_value
                        elif nodedcfname_result:
                            if not is_string(computed_value):
                                raise SyntaxError, _("Invalid value \"%s\" for keyname \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                            nodeid = int(nodedcfname_result.groups()[0])
                            if nodeid not in topology["Nodes"].keys():
                                topology["Nodes"][nodeid] = {}
                            topology["Nodes"][nodeid]["DCFName"] = computed_value
                        else:
                            raise SyntaxError, _("Keyname \"%s\" not recognised for section \"[%s]\"")%(keyname, section_name)
                        
                # All lines that are not empty and are neither a comment neither not a valid assignment
                elif assignment.strip() != "":
                    raise SyntaxError, _("\"%s\" is not a valid CPJ line")%assignment.strip()
        
            if "Number" not in topology.keys():
                raise SyntaxError, _("\"Nodes\" keyname in \"[%s]\" section is missing")%section_name
        
            if topology["Number"] != len(topology["Nodes"]):
                raise SyntaxError, _("\"Nodes\" value not corresponding to number of nodes defined")
            
            for nodeid, node in topology["Nodes"].items():
                if "Present" not in node.keys():
                    raise SyntaxError, _("\"Node%dPresent\" keyname in \"[%s]\" section is missing")%(nodeid, section_name)
            
            networks.append(topology)
            
        # In other case, there is a syntax problem into CPJ file
        else:
            raise SyntaxError, _("Section \"[%s]\" is unrecognized")%section_name
    
    return networks

# Function that parse an EDS file and returns a dictionary of the informations
def ParseEDSFile(filepath):
    eds_dict = {}
    # Read file text
    eds_file = open(filepath,'r').read()
    sections = ExtractSections(eds_file)
    
    # Parse assignments for each section
    for section_name, assignments in sections:
        # Reset values of entry
        values = {}
        
        # Search if the section name match an index or subindex expression
        index_result = index_model.match(section_name.upper())
        subindex_result = subindex_model.match(section_name.upper())
        index_objectlinks_result = index_objectlinks_model.match(section_name.upper())
        
        # Compilation of the EDS information dictionary
        
        is_entry = False
        # First case, section name is in SECTION_KEYNAMES 
        if section_name.upper() in SECTION_KEYNAMES:
            # Verify that entry is not already defined
            if section_name.upper() not in eds_dict:
                eds_dict[section_name.upper()] = values
            else:
                raise SyntaxError, _("\"[%s]\" section is defined two times")%section_name
        # Second case, section name is an index name 
        elif index_result:
            # Extract index number
            index = int(index_result.groups()[0], 16)
            # If index hasn't been referenced before, we add an entry into the dictionary
            if index not in eds_dict:
                eds_dict[index] = values
                eds_dict[index]["subindexes"] = {}
            elif eds_dict[index].keys() == ["subindexes"]:
                values["subindexes"] = eds_dict[index]["subindexes"]
                eds_dict[index] = values
            else:
                raise SyntaxError, _("\"[%s]\" section is defined two times")%section_name
            is_entry = True
        # Third case, section name is a subindex name 
        elif subindex_result:
            # Extract index and subindex number
            index, subindex = [int(value, 16) for value in subindex_result.groups()]
            # If index hasn't been referenced before, we add an entry into the dictionary
            # that will be updated later
            if index not in eds_dict:
                eds_dict[index] = {"subindexes" : {}}
            if subindex not in eds_dict[index]["subindexes"]:
                eds_dict[index]["subindexes"][subindex] = values
            else:
                raise SyntaxError, _("\"[%s]\" section is defined two times")%section_name
            is_entry = True
        # Third case, section name is a subindex name 
        elif index_objectlinks_result:
            pass
        # In any other case, there is a syntax problem into EDS file
        else:
            raise SyntaxError, _("Section \"[%s]\" is unrecognized")%section_name
        
        for assignment in assignments:
            # Escape any comment
            if assignment.startswith(";"):
                pass
            # Verify that line is a valid assignment
            elif assignment.find('=') > 0:
                # Split assignment into the two values keyname and value
                keyname, value = assignment.split("=", 1)
                
                # keyname must be immediately followed by the "=" sign, so we
                # verify that there is no whitespace into keyname
                if keyname.isalnum():
                    # value can be preceded and followed by whitespaces, so we escape them
                    value = value.strip()
                    # First case, value starts with "$NODEID", then it's a formula
                    if value.upper().startswith("$NODEID"):
                        try:
                            test = int(value.upper().replace("$NODEID+", ""), 16)
                            computed_value = "\"%s\""%value
                        except:
                            raise SyntaxError, _("\"%s\" is not a valid formula for attribute \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                    # Second case, value starts with "0x", then it's an hexadecimal value
                    elif value.startswith("0x") or value.startswith("-0x"):
                        try:
                            computed_value = int(value, 16)
                        except:
                            raise SyntaxError, _("\"%s\" is not a valid value for attribute \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                    elif value.isdigit() or value.startswith("-") and value[1:].isdigit():
                        # Third case, value is a number and starts with "0", then it's an octal value
                        if value.startswith("0") or value.startswith("-0"):
                            computed_value = int(value, 8)
                        # Forth case, value is a number and don't start with "0", then it's a decimal value
                        else:
                            computed_value = int(value)
                    # In any other case, we keep string value
                    else:
                        computed_value = value
                    
                    # Add value to values dictionary
                    if computed_value != "":
                        # If entry is an index or a subindex
                        if is_entry:
                            # Verify that keyname is a possible attribute
                            if keyname.upper() not in ENTRY_ATTRIBUTES:
                                raise SyntaxError, _("Keyname \"%s\" not recognised for section \"[%s]\"")%(keyname, section_name)
                            # Verify that value is valid
                            elif not ENTRY_ATTRIBUTES[keyname.upper()](computed_value):
                                raise SyntaxError, _("Invalid value \"%s\" for keyname \"%s\" of section \"[%s]\"")%(value, keyname, section_name)
                            else:
                                values[keyname.upper()] = computed_value
                        else:
                            values[keyname.upper()] = computed_value
            # All lines that are not empty and are neither a comment neither not a valid assignment
            elif assignment.strip() != "":
                raise SyntaxError, _("\"%s\" is not a valid EDS line")%assignment.strip()
        
        # If entry is an index or a subindex
        if is_entry:
            # Verify that entry has an ObjectType
            values["OBJECTTYPE"] = values.get("OBJECTTYPE", 7)
            # Extract parameters defined
            keys = set(values.keys())
            keys.discard("subindexes")
            # Extract possible parameters and parameters required
            possible = set(ENTRY_TYPES[values["OBJECTTYPE"]]["require"] + 
                           ENTRY_TYPES[values["OBJECTTYPE"]]["optional"])
            required = set(ENTRY_TYPES[values["OBJECTTYPE"]]["require"])
            # Verify that parameters defined contains all the parameters required
            if not keys.issuperset(required):
                missing = required.difference(keys)._data.keys()
                if len(missing) > 1:
                    attributes = _("Attributes %s are")%_(", ").join(["\"%s\""%attribute for attribute in missing])
                else:
                    attributes = _("Attribute \"%s\" is")%missing[0]
                raise SyntaxError, _("Error on section \"[%s]\":\n%s required for a %s entry")%(section_name, attributes, ENTRY_TYPES[values["OBJECTTYPE"]]["name"])
            # Verify that parameters defined are all in the possible parameters
            if not keys.issubset(possible):
                unsupported = keys.difference(possible)._data.keys()
                if len(unsupported) > 1:
                    attributes = _("Attributes %s are")%_(", ").join(["\"%s\""%attribute for attribute in unsupported])
                else:
                    attributes = _("Attribute \"%s\" is")%unsupported[0]
                raise SyntaxError, _("Error on section \"[%s]\":\n%s unsupported for a %s entry")%(section_name, attributes, ENTRY_TYPES[values["OBJECTTYPE"]]["name"])
            
            VerifyValue(values, section_name, "ParameterValue")
            VerifyValue(values, section_name, "DefaultValue")
            
    return eds_dict

def VerifyValue(values, section_name, param):
    if param.upper() in values:
        try:
            if values["DATATYPE"] in (0x09, 0x0A, 0x0B, 0x0F):
                values[param.upper()] = str(values[param.upper()])
            elif values["DATATYPE"] in (0x08, 0x11):
                values[param.upper()] = float(values[param.upper()])
            elif values["DATATYPE"] == 0x01:
                values[param.upper()] = {0 : False, 1 : True}[values[param.upper()]]
            else:
                if not isinstance(values[param.upper()], (IntType, LongType)) and values[param.upper()].upper().find("$NODEID") == -1:
                    raise
        except:
            raise SyntaxError, _("Error on section \"[%s]\":\n%s incompatible with DataType")%(section_name, param)


# Function that write an EDS file after generate it's content
def WriteFile(filepath, content):
    # Open file in write mode
    cfile = open(filepath,"w")
    # Write content
    cfile.write(content)
    # Close file
    cfile.close()


# Function that generate the EDS file content for the current node in the manager
def GenerateFileContent(Node, filepath):
    # Dictionary of each index contents
    indexContents = {}
    
    # Extract local time
    current_time = localtime()
    # Extract node informations
    nodename = Node.GetNodeName()
    nodeid = Node.GetNodeID()
    nodetype = Node.GetNodeType() 
    description = Node.GetNodeDescription()
    
    # Retreiving lists of indexes defined
    entries = Node.GetIndexes()
    
    # Generate FileInfo section
    fileContent = "[FileInfo]\n"
    fileContent += "FileName=%s\n"%os.path.split(filepath)[-1]
    fileContent += "FileVersion=1\n"
    fileContent += "FileRevision=1\n"
    fileContent += "EDSVersion=4.0\n"
    fileContent += "Description=%s\n"%description
    fileContent += "CreationTime=%s"%strftime("%I:%M", current_time)
    # %p option of strftime seems not working, then generate AM/PM by hands
    if strftime("%I", current_time) == strftime("%H", current_time):
        fileContent += "AM\n"
    else:
        fileContent += "PM\n"
    fileContent += "CreationDate=%s\n"%strftime("%m-%d-%Y", current_time)
    fileContent += "CreatedBy=CANFestival\n"
    fileContent += "ModificationTime=%s"%strftime("%I:%M", current_time)
    # %p option of strftime seems not working, then generate AM/PM by hands
    if strftime("%I", current_time) == strftime("%H", current_time):
        fileContent += "AM\n"
    else:
        fileContent += "PM\n"
    fileContent += "ModificationDate=%s\n"%strftime("%m-%d-%Y", current_time)
    fileContent += "ModifiedBy=CANFestival\n"
    
    # Generate DeviceInfo section
    fileContent += "\n[DeviceInfo]\n"
    fileContent += "VendorName=CANFestival\n"
    # Use information typed by user in Identity entry
    fileContent += "VendorNumber=0x%8.8X\n"%Node.GetEntry(0x1018, 1)
    fileContent += "ProductName=%s\n"%nodename
    fileContent += "ProductNumber=0x%8.8X\n"%Node.GetEntry(0x1018, 2)
    fileContent += "RevisionNumber=0x%8.8X\n"%Node.GetEntry(0x1018, 3)
    # CANFestival support all baudrates as soon as driver choosen support them
    fileContent += "BaudRate_10=1\n"
    fileContent += "BaudRate_20=1\n"
    fileContent += "BaudRate_50=1\n"
    fileContent += "BaudRate_125=1\n"
    fileContent += "BaudRate_250=1\n"
    fileContent += "BaudRate_500=1\n"
    fileContent += "BaudRate_800=1\n"
    fileContent += "BaudRate_1000=1\n"
    # Select BootUp type from the informations given by user
    fileContent += "SimpleBootUpMaster=%s\n"%BOOL_TRANSLATE[nodetype == "master"]
    fileContent += "SimpleBootUpSlave=%s\n"%BOOL_TRANSLATE[nodetype == "slave"]
    # CANFestival characteristics
    fileContent += "Granularity=8\n"
    fileContent += "DynamicChannelsSupported=0\n"
    fileContent += "CompactPDO=0\n"
    fileContent += "GroupMessaging=0\n"
    # Calculate receive and tranmit PDO numbers with the entry available
    fileContent += "NrOfRXPDO=%d\n"%len([idx for idx in entries if 0x1400 <= idx <= 0x15FF])
    fileContent += "NrOfTXPDO=%d\n"%len([idx for idx in entries if 0x1800 <= idx <= 0x19FF])
    # LSS not supported as soon as DS-302 was not fully implemented
    fileContent += "LSS_Supported=0\n"
    
    # Generate Dummy Usage section
    fileContent += "\n[DummyUsage]\n"
    fileContent += "Dummy0001=0\n"
    fileContent += "Dummy0002=1\n"
    fileContent += "Dummy0003=1\n"
    fileContent += "Dummy0004=1\n"
    fileContent += "Dummy0005=1\n"
    fileContent += "Dummy0006=1\n"
    fileContent += "Dummy0007=1\n"

    # Generate Comments section
    fileContent += "\n[Comments]\n"
    fileContent += "Lines=0\n"
    
    # List of entry by type (Mandatory, Optional or Manufacturer
    mandatories = []
    optionals = []
    manufacturers = []
    
    # Remove all unused PDO
##    for entry in entries[:]:
##        if 0x1600 <= entry < 0x1800 or 0x1A00 <= entry < 0x1C00:
##            subentry_value = Node.GetEntry(entry, 1)
##            if subentry_value is None or subentry_value == 0:
##                entries.remove(entry)
##                entries.remove(entry - 0x200)
                
    # For each entry, we generate the entry section or sections if there is subindexes
    for entry in entries:
        # Extract infos and values for the entry
        entry_infos = Node.GetEntryInfos(entry)
        values = Node.GetEntry(entry, compute = False)
        # Define section name
        text = "\n[%X]\n"%entry
        # If there is only one value, it's a VAR entry
        if type(values) != ListType:
            # Extract the informations of the first subindex
            subentry_infos = Node.GetSubentryInfos(entry, 0)
            # Generate EDS informations for the entry
            text += "ParameterName=%s\n"%subentry_infos["name"]
            text += "ObjectType=0x7\n"
            text += "DataType=0x%4.4X\n"%subentry_infos["type"]
            text += "AccessType=%s\n"%subentry_infos["access"]
            if subentry_infos["type"] == 1:
                text += "DefaultValue=%s\n"%BOOL_TRANSLATE[values]
            else:
                text += "DefaultValue=%s\n"%values
            text += "PDOMapping=%s\n"%BOOL_TRANSLATE[subentry_infos["pdo"]]
        else:
            # Generate EDS informations for the entry
            text += "ParameterName=%s\n"%entry_infos["name"]
            if entry_infos["struct"] & node.OD_IdenticalSubindexes:
                text += "ObjectType=0x9\n"
            else:
                text += "ObjectType=0x8\n"
            
            # Generate EDS informations for subindexes of the entry in a separate text
            subtext = ""
            # Reset number of subindex defined 
            nb_subentry = 0
            for subentry, value in enumerate(values):
                # Extract the informations of each subindex
                subentry_infos = Node.GetSubentryInfos(entry, subentry)
                # If entry is not for the compatibility, generate informations for subindex
                if subentry_infos["name"] != "Compatibility Entry":
                    subtext += "\n[%Xsub%X]\n"%(entry, subentry)
                    subtext += "ParameterName=%s\n"%subentry_infos["name"]
                    subtext += "ObjectType=0x7\n"
                    subtext += "DataType=0x%4.4X\n"%subentry_infos["type"]
                    subtext += "AccessType=%s\n"%subentry_infos["access"]
                    if subentry_infos["type"] == 1:
                        subtext += "DefaultValue=%s\n"%BOOL_TRANSLATE[value]
                    else:
                        subtext += "DefaultValue=%s\n"%value
                    subtext += "PDOMapping=%s\n"%BOOL_TRANSLATE[subentry_infos["pdo"]]
                    # Increment number of subindex defined 
                    nb_subentry += 1
            # Write number of subindex defined for the entry
            text += "SubNumber=%d\n"%nb_subentry
            # Write subindex definitions
            text += subtext
        
        # Then we add the entry in the right list
        
        # First case, entry is between 0x2000 and 0x5FFF, then it's a manufacturer entry
        if 0x2000 <= entry <= 0x5FFF:
            manufacturers.append(entry)
        # Second case, entry is required, then it's a mandatory entry
        elif entry_infos["need"]:
            mandatories.append(entry)
        # In any other case, it's an optional entry
        else:
            optionals.append(entry)
        # Save text of the entry in the dictiionary of contents
        indexContents[entry] = text
    
    # Before generate File Content we sort the entry list
    manufacturers.sort()
    mandatories.sort()
    optionals.sort()
    
    # Generate Definition of mandatory objects
    fileContent += "\n[MandatoryObjects]\n"
    fileContent += "SupportedObjects=%d\n"%len(mandatories)
    for idx, entry in enumerate(mandatories):
        fileContent += "%d=0x%4.4X\n"%(idx + 1, entry)
    # Write mandatory entries
    for entry in mandatories:
        fileContent += indexContents[entry]
    
    # Generate Definition of optional objects
    fileContent += "\n[OptionalObjects]\n"
    fileContent += "SupportedObjects=%d\n"%len(optionals)
    for idx, entry in enumerate(optionals):
        fileContent += "%d=0x%4.4X\n"%(idx + 1, entry)
    # Write optional entries
    for entry in optionals:
        fileContent += indexContents[entry]

    # Generate Definition of manufacturer objects
    fileContent += "\n[ManufacturerObjects]\n"
    fileContent += "SupportedObjects=%d\n"%len(manufacturers)
    for idx, entry in enumerate(manufacturers):
        fileContent += "%d=0x%4.4X\n"%(idx + 1, entry)
    # Write manufacturer entries
    for entry in manufacturers:
        fileContent += indexContents[entry]
    
    # Return File Content
    return fileContent


# Function that generates EDS file from current node edited
def GenerateEDSFile(filepath, node):
    try:
        # Generate file content
        content = GenerateFileContent(node, filepath)
        # Write file
        WriteFile(filepath, content)
        return None
    except ValueError, message:
        return _("Unable to generate EDS file\n%s")%message
    
# Function that generate the CPJ file content for the nodelist
def GenerateCPJContent(nodelist):
    nodes = nodelist.SlaveNodes.keys()
    nodes.sort()
    
    fileContent = "[TOPOLOGY]\n"
    fileContent += "NetName=%s\n"%nodelist.GetNetworkName()
    fileContent += "Nodes=0x%2.2X\n"%len(nodes)
    
    for nodeid in nodes:
        fileContent += "Node%dPresent=0x01\n"%nodeid
        fileContent += "Node%dName=%s\n"%(nodeid, nodelist.SlaveNodes[nodeid]["Name"])
        fileContent += "Node%dDCFName=%s\n"%(nodeid, nodelist.SlaveNodes[nodeid]["EDS"])
        
    fileContent += "EDSBaseName=eds\n"
    return fileContent

# Function that generates Node from an EDS file
def GenerateNode(filepath, nodeID = 0):
    # Create a new node
    Node = node.Node(id = nodeID)
    try:
        # Parse file and extract dictionary of EDS entry
        eds_dict = ParseEDSFile(filepath)
        # Extract Profile Number from Device Type entry
        ProfileNb = eds_dict[0x1000].get("DEFAULTVALUE", 0) & 0x0000ffff
        # If profile is not DS-301 or DS-302
        if ProfileNb not in [0, 301, 302]:
            # Compile Profile name and path to .prf file
            ProfileName = "DS-%d"%ProfileNb
            ProfilePath = os.path.join(os.path.split(__file__)[0], "config/%s.prf"%ProfileName)
            # Verify that profile is available
            if os.path.isfile(ProfilePath):
                try:
                    # Load Profile
                    execfile(ProfilePath)
                    Node.SetProfileName(ProfileName)
                    Node.SetProfile(Mapping)
                    Node.SetSpecificMenu(AddMenuEntries)
                except:
                    pass
        # Read all entries in the EDS dictionary 
        for entry, values in eds_dict.iteritems():
            # All sections with a name in keynames are escaped
            if entry in SECTION_KEYNAMES:
                pass
            else:
                # Extract informations for the entry
                entry_infos = Node.GetEntryInfos(entry)
                
                # If no informations are available, then we write them
                if not entry_infos:
                    # First case, entry is a DOMAIN or VAR
                    if values["OBJECTTYPE"] in [2, 7]:
                        if values["OBJECTTYPE"] == 2:
                            values["DATATYPE"] = values.get("DATATYPE", 0xF)
                            if values["DATATYPE"] != 0xF:
                                raise SyntaxError, _("Domain entry 0x%4.4X DataType must be 0xF(DOMAIN) if defined")%entry
                        # Add mapping for entry
                        Node.AddMappingEntry(entry, name = values["PARAMETERNAME"], struct = 1)
                        # Add mapping for first subindex
                        Node.AddMappingEntry(entry, 0, values = {"name" : values["PARAMETERNAME"], 
                                                                 "type" : values["DATATYPE"], 
                                                                 "access" : ACCESS_TRANSLATE[values["ACCESSTYPE"].upper()], 
                                                                 "pdo" : values.get("PDOMAPPING", 0) == 1})
                    # Second case, entry is an ARRAY or RECORD
                    elif values["OBJECTTYPE"] in [8, 9]:
                        # Extract maximum subindex number defined
                        max_subindex = max(values["subindexes"].keys())
                        # Add mapping for entry
                        Node.AddMappingEntry(entry, name = values["PARAMETERNAME"], struct = 3)
                        # Add mapping for first subindex
                        Node.AddMappingEntry(entry, 0, values = {"name" : "Number of Entries", "type" : 0x05, "access" : "ro", "pdo" : False})
                        # Add mapping for other subindexes
                        for subindex in xrange(1, int(max_subindex) + 1):
                            # if subindex is defined
                            if subindex in values["subindexes"]:
                                Node.AddMappingEntry(entry, subindex, values = {"name" : values["subindexes"][subindex]["PARAMETERNAME"], 
                                                                                "type" : values["subindexes"][subindex]["DATATYPE"], 
                                                                                "access" : ACCESS_TRANSLATE[values["subindexes"][subindex]["ACCESSTYPE"].upper()], 
                                                                                "pdo" : values["subindexes"][subindex].get("PDOMAPPING", 0) == 1})
                            # if not, we add a mapping for compatibility 
                            else:
                                Node.AddMappingEntry(entry, subindex, values = {"name" : "Compatibility Entry", "type" : 0x05, "access" : "rw", "pdo" : False})
##                    # Third case, entry is an RECORD
##                    elif values["OBJECTTYPE"] == 9:
##                        # Verify that the first subindex is defined
##                        if 0 not in values["subindexes"]:
##                            raise SyntaxError, "Error on entry 0x%4.4X:\nSubindex 0 must be defined for a RECORD entry"%entry
##                        # Add mapping for entry
##                        Node.AddMappingEntry(entry, name = values["PARAMETERNAME"], struct = 7)
##                        # Add mapping for first subindex
##                        Node.AddMappingEntry(entry, 0, values = {"name" : "Number of Entries", "type" : 0x05, "access" : "ro", "pdo" : False})
##                        # Verify that second subindex is defined
##                        if 1 in values["subindexes"]:
##                            Node.AddMappingEntry(entry, 1, values = {"name" : values["PARAMETERNAME"] + " %d[(sub)]", 
##                                                                     "type" : values["subindexes"][1]["DATATYPE"], 
##                                                                     "access" : ACCESS_TRANSLATE[values["subindexes"][1]["ACCESSTYPE"].upper()], 
##                                                                     "pdo" : values["subindexes"][1].get("PDOMAPPING", 0) == 1,
##                                                                     "nbmax" : 0xFE})
##                        else:
##                            raise SyntaxError, "Error on entry 0x%4.4X:\nA RECORD entry must have at least 2 subindexes"%entry
                
                # Define entry for the new node
                
                # First case, entry is a DOMAIN or VAR
                if values["OBJECTTYPE"] in [2, 7]:
                    # Take default value if it is defined
                    if "PARAMETERVALUE" in values:
                        value = values["PARAMETERVALUE"]
                    elif "DEFAULTVALUE" in values:
                        value = values["DEFAULTVALUE"]
                    # Find default value for value type of the entry
                    else:
                        value = GetDefaultValue(Node, entry)
                    Node.AddEntry(entry, 0, value)
                # Second case, entry is an ARRAY or a RECORD
                elif values["OBJECTTYPE"] in [8, 9]:
                    # Verify that "Subnumber" attribute is defined and has a valid value
                    if "SUBNUMBER" in values and values["SUBNUMBER"] > 0:
                        # Extract maximum subindex number defined
                        max_subindex = max(values["subindexes"].keys())
                        Node.AddEntry(entry, value = [])
                        # Define value for all subindexes except the first 
                        for subindex in xrange(1, int(max_subindex) + 1):
                            # Take default value if it is defined and entry is defined
                            if subindex in values["subindexes"] and "PARAMETERVALUE" in values["subindexes"][subindex]:
                                value = values["subindexes"][subindex]["PARAMETERVALUE"]
                            elif subindex in values["subindexes"] and "DEFAULTVALUE" in values["subindexes"][subindex]:
                                value = values["subindexes"][subindex]["DEFAULTVALUE"]
                            # Find default value for value type of the subindex
                            else:
                                value = GetDefaultValue(Node, entry, subindex)
                            Node.AddEntry(entry, subindex, value)
                    else:
                        raise SyntaxError, _("Array or Record entry 0x%4.4X must have a \"SubNumber\" attribute")%entry
        return Node
    except SyntaxError, message:
        return _("Unable to import EDS file\n%s")%message

#-------------------------------------------------------------------------------
#                             Main Function
#-------------------------------------------------------------------------------

if __name__ == '__main__':
    print ParseEDSFile("examples/PEAK MicroMod.eds")