import sys, string import lxml.etree import re import plcData from plcElement import plcElementXML from plcDataType import plcDataType, plcCustomData from plcElement import UselessDataType #=============================================================================== # Class to deal with the RSLogix500 tags in the XML files # This class can be for Alias tags, but it will create a daughter alias tag of # plcAliasTag class to deal with aliases. This will deal directly with # Tag, InputTag, OutputTag, ConfigTag XML tags #=============================================================================== class plcTag ( plcElementXML ): """Allen-Bradley PLC RSLogix5000 Tag""" nUnNamed = 0 # Constructor of the tag from XML element def __init__(self, rootElement, parentObj=None, forcedName=None ): try: plcElementXML.__init__(self, rootElement, parentObj, forcedName ) except UselessDataType as err: #print(err) return self.structElms = None self.structures = [] self.customData = [] self.arrayElements = [] self.aliasTags = [] self.daughters.append( self.structures ) self.daughters.append( self.arrayElements ) self.daughters.append( self.aliasTags ) self.daughters.append( self.customData ) # For tags name contributes to the path self.addNameToPath( self.name ) # If it is an alias create an alias tag and store it in the daughters and return # The alias tag will have no name, the daughter plcAliasTag itself will not contribute to path if( "TagType" in list(self.root.attrib.keys()) and self.root.attrib["TagType"] == "Alias" and "AliasFor" in list(self.root.attrib.keys()) ): aliasTag = plcAliasTag( self.root, self, "" ) self.aliasTags.append( aliasTag ) return # If we got here then the tag is not an alias to another tag # Refer to plcAliasTag class to what happens to the aliases # Check if the tag is an array and set the flag for future use self.isArray = False if self.dimensions is not None: self.isArray = True self.createChildren() return # In this method we generate the child objects of the tag # this method is designed to be called from the constractor def createChildren(self): if (self.dataType != "") and (self.dataType in plcData.atomicTypes): # This tag is PLC atomic type, not sure yet if it is scalar or array if not self.isArray: # This is a scalar number of atomic type. Define the scaler data and move on dataValue = None dataRadix = "" if "Radix" in list(self.root.attrib.keys()): dataRadix = self.root.attrib["Radix"] if "Value" in list(self.root.attrib.keys()): dataValue = self.root.attrib["Value"] self.scalar = plcData.plcScalar( self.path, self.dataType, self.accessLevel, dataValue, dataRadix ) else: # This is an array if atomic elements. Create an array of atomic elements self.array = plcData.plcArray( self.path, self.dataType, self.accessLevel, self.dimensions ) else: # This is not an atomic type tag, not sure yet scalar or array if not self.isArray: # If the DataType is not a PLC type and is not array, then it has to be a structure or custom data type # Get all structure elements in the tag element. There should be only one! self.structElms = self.root.xpath("Data[@Format='Decorated']/Structure") if len(self.structElms) != 1: # The tag is with predefined DataTypes that does not happen to have a structure tag in it. # if len(self.structElms) == 0: print("Tag <{0}> of type <{1}> does not contain data structure".format(self.name, self.dataType)) controllerObject = self.getParentController() if self.dataType in controllerObject.dataTypes.keys(): print( "Creating custom data with data type {0}".format(self.dataType) ) newCustomData = plcCustomData( self.root, self.parent ) self.customData.append(newCustomData) for structElm in self.structElms: #print("Data Structure: ", structElm.attrib) # For tags that do not have name but the structure does , we may want to use # the structure name in the path. Need to switch if decide to it that way if self.name == "" and "DataType" in list(structElm.attrib.keys()): # self.path += plcElementXML.elementSeparator + structElm.attrib["DataType"] self.name = "UnNamedTag" + str(plcTag.nUnNamed) self.addNameToPath( self.name ) # self.addNameToPath( structElm.attrib["DataType"] ) plcTag.nUnNamed += 1 elif self.name == "": self.name = "UnNamedTag" + str(plcTag.nUnNamed) self.addNameToPath( self.name ) plcTag.nUnNamed += 1 # Create the new structure object and store it in the list of structures newStruct = plcStructure( structElm, self, None ) self.structures.append( newStruct ) else: # This is an array of user-defined structures. Find all Elements define under it in the XML file # and create corresponding ArrayElement objects. arrayElms = self.root.xpath("Data/Array[@DataType=$dt]/Element", dt = self.dataType ) for arrElm in arrayElms: newArrElm = plcArrayElement( arrElm, self ) self.arrayElements.append( newArrElm ) return def __repr__(self): retString = "<{0}>".format( self.name ) if self.scalar is not None: retString += "Has scalar tag <{0}>".format(self.path) + "\n" if self.array is not None: retString += "Has array tag <{0}>".format(self.path) + "\n" return retString + plcElementXML.__repr__(self) def __str__(self): retString = "<{0}>".format( self.name ) if self.scalar is not None: retString += "Has scalar tag <{0}>".format(self.path) + "\n" if self.array is not None: retString += "Has array tag <{0}>".format(self.path) + "\n" return retString + plcElementXML.__repr__(self) #============================================================================== # Class to handle DataValueMember elements in the RSLogix5000 XML file #============================================================================== class plcDataValueMember( plcElementXML ): """Class to handle DataValueMember elements in the RSLogix5000 XML file """ # Constructor def __init__( self, rootElement, parentObj, forcedName=None ): try: plcElementXML.__init__(self, rootElement, parentObj, forcedName ) except UselessDataType as err: #print(err) return #Inherit access level if parentObj is not None: self.accessLevel = parentObj.accessLevel self.value = "" self.radix = "" # self.scalar = None # For tags name contributes to the path self.addNameToPath( self.name ) # Data value member has to have a name if self.name == "": print("No name specified for DataValueMember of tag <{0}>".format( self.parent.name )) sys.exit(-1) # Data value member has to have a type if self.dataType == "": print("DataType is no specified for DataValueMember <{0}>".format( self.name )) sys.exit(-1) # Get default value and "Radix" if "Value" in list(rootElement.attrib.keys()): self.value = rootElement.attrib["Value"] if "Radix" in list(rootElement.attrib.keys()): self.radix = rootElement.attrib["Radix"] # Create the scalar data object and store its reference in the objects attribute self.scalar = plcData.plcScalar( self.path, self.dataType, self.accessLevel, self.value, self.radix ) # Set the parent data type to the data type of the data (the structure is supposed to be its parent). # This will be used in case of the AddOn instructions where the access level is changed. self.scalar.structureType = parentObj.getStructureType() return def __repr__(self): return repr(self.scalar) + "\n" def __str__(self): return str(self.scalar) + "\n" #=============================================================================== # Class to handle ArrayMember elements in the PLC XML file # This class is only supposed to handle the atomic type arrays #=============================================================================== class plcArrayMember( plcElementXML ): """ Class to handle ArrayMember structures in the PLC XML file """ # Constructor def __init__( self, rootElement, parentObj=None, forcedName=None ): # Inherit from plcTag since it does almost the same. # Finds the structures or sees that is an arrays for scaler tags try: plcElementXML.__init__(self, rootElement, parentObj, forcedName ) except UselessDataType as err: #print(err) return #Inherit access level if parentObj is not None: self.accessLevel = parentObj.accessLevel # self.array = None self.arrayElements = [] self.daughters.append(self.arrayElements) # For tags name contributes to the path self.addNameToPath( self.name ) # Array has to have a type if self.dataType == "": print("ArrayMember <{0}> does not have DataType".format( self.name )) sys.exit(-1) # Array has to have dimensions if self.dimensions is None or self.dimensions == "": print("ArrayMember <{0}> does not have dimensions".format( self.name )) sys.exit(-1) # Create the correct object and store it if self.dataType in plcData.atomicTypes: # This is an array of atomic type self.array = plcData.plcArray( self.path, self.dataType, self.accessLevel, self.dimensions ) # Set the parent data type to the data type of the data (the structure is supposed to be its parent). # This will be used in case of the AddOn instructions where the access level is changed. self.array.structureType = parentObj.getStructureType() else: # This is an array of user-specified structures # Loop through the elements and build the corresponding objects arrayElms = self.root.xpath("Element" ) for arrElm in arrayElms: newArrElm = plcArrayElement( arrElm, self ) self.arrayElements.append( newArrElm ) # print "Completed Array member <{0}>".format(self.name) + str(self) return #=============================================================================== # Class to handle cases when an array is an array for non-atomic types. #=============================================================================== class plcArrayElement( plcElementXML ): """ Class to handle cases when an array is an array for non-atomic types """ # Constructor def __init__(self, rootElement, parentObj=None, forcedName=None ): try: plcElementXML.__init__(self, rootElement, parentObj, forcedName ) except UselessDataType as err: #print(err) return #Inherit access level if parentObj is not None: self.accessLevel = parentObj.accessLevel self.structures = [] self.daughters.append( self.structures ) # ArrayElement (which corresponds to the Element XML tag) has to have Index attribute if "Index" not in list(self.root.attrib.keys()): print("Structure in the array for tag <{0}> does not have Index. Exiting...".format(self.parent.name)) sys.exit(-1) self.index = self.root.attrib["Index"] # At this point we do not deal with the index, just keep it as it is # If it needs to be linearized, we will have to do at a later stage self.name += self.index # Remove the dot from the end of the path and add the index piece to it self.removeLastDotFromPath() self.addNameToPath( self.index ) # Find all daughter Structure XML elements and build the corresponding objects structElms = self.root.xpath( "Structure") for structElm in structElms : newStruct = plcStructure( structElm, self, None ) self.structures.append(newStruct) return #=============================================================================== # Class to handle Alias Tags. It inherits from the regular plcElementXML class #=============================================================================== class plcAliasTag( plcElementXML ): """Class to handle RSLogix5000 PLC Aliases in XML file """ # Constructor def __init__( self, rootElement, parentObj=None, forcedName=None ): try: plcElementXML.__init__(self, rootElement, parentObj, forcedName ) except UselessDataType as err: #print(err) return self.targets = [] # self.scalar = None self.daughters.append(self.targets) # Double check that this is an Alias tag in the XML file. If not then exit. if( "AliasFor" not in list(self.root.attrib.keys()) or "TagType" not in list(self.root.attrib.keys()) or self.root.attrib["TagType"] != "Alias" ): print("Either AliasFor or TagType attributes missing for alias tag <{0}>. Exiting...".format( self.root.name )) sys.exit(-1) # Find the target of this alias to get the datatype or the substructure for it self.tgtName = self.root.attrib["AliasFor"] isArrayElement = self.checkPattern4ArrayElement( self.tgtName ) isBitElement = self.checkPattern4BitElement( self.tgtName ) if not isArrayElement and not isBitElement: # The alias target is neither an array element nor a bit of an integral type # It is the most straightforward case when the alias is defined by actual target tag name # Find the target element such that the path of the object is identical to the name # given in the alias definition in the XML file. This is done so that the internal structure, # if any can be traced tgtTag = self.findTargetElm( self.tgtName ) if tgtTag is not None: tgtElm = tgtTag.root # Create names less plcTag or plcAliasTag objects and store them if "DataType" in list(tgtElm.attrib.keys()) and tgtElm.attrib["DataType"] in plcData.atomicTypes: # Target is not an alias, the target will have empty name newTag = plcTag( tgtElm, self, "" ) self.targets.append( newTag ) else: # The target will be an alias too, the target will have empty name # newAliasTag = plcAliasTag( tgtElm, self, "" ) newTag = plcTag( tgtElm, self, "" ) self.targets.append( newTag ) else: # Did not find the target by that name (path to be precise) print("No target found for Alias <{0}>".format(self.name)) elif isArrayElement: # It is an element of an array. It cannot be an alias # print "Looking for array element for alias <{0}>".format( self.tgtName ) strippedName = self.stripAliasBrackets( self.tgtName ) # Strip the bracket part off from the right side tgtTag = self.findTargetElm( strippedName ) # Find the target that corresponds to the array name # Keep stripping the last brackets until the tagret is not an array element while tgtTag is not None and self.checkPattern4ArrayElement(tgtTag.name): strippedName = self.stripAliasBrackets( self.tgtName ) # Strip the bracket part off from the right side tgtTag = self.findTargetElm( strippedName ) # Once the target is not an array element we will be able to determine the type of the alias tag if tgtTag is not None: # At least one target was found for the array # Get the structure of the array element based on the structure of the array found if tgtTag.dataType in plcData.atomicTypes: # Atomic type. Add the bracket string to the path, create a scaler and keep the value in the scalar list # self.addNameToPath( self.name ) self.scalar = plcData.plcScalar( self.path, tgtTag.dataType, self.accessLevel, None, None ) else: # Target is a user-defined tructure parentTag = plcTag( tgtTag.root, self, "" ) # Array tag will be target, no name self.targets.append( parentTag ) else: # Did not find the target by that name (path to be precise) print("No target found for Alias <{0}>, was looking for array <{1}>".format( self.name, strippedName )) elif isBitElement: # Since we know it is a bit, we do not care what the target is, and we simply create a BIT scalar object self.scalar = plcData.plcScalar( self.path, "BIT", self.accessLevel,None, "Binary" ) # print "Finished dealing with alias", str(self) + "\n" return # Returns True if aliasName matches the pattern of an array element based on brackets at the end def checkPattern4ArrayElement( self, aliasName ): # Pattern that array elements have, multidimensional is allowed, spaces not allowed. the last piece should be like [14,12] etc arrElmPattern = re.compile( "^([\w:\.]*)\[(\d{1,5}\,){0,6}\d{1,5}\]$" ) return arrElmPattern.match(aliasName) is not None # Returns True if aliasName matches pattern of a bit set element based the dot followed by a number def checkPattern4BitElement(self, aliasName ): # Pattern that bit members have, the last peice should be like ".15" bitPattern = re.compile( "^([\w:\.\[\]]*)\.(\d{1,5})$" ) return (bitPattern.match(self.tgtName) is not None) # Strips the bracket part of the path by finding the rightmost "[" and returning substring before it def stripAliasBrackets(self, inString): lastBracketPosition = inString.rfind( "[" ) # Find the right most right square bracket if lastBracketPosition > 0: return inString[0:lastBracketPosition] # Return the substring before the right bracket else: return None # Strips the part after last dot, including the dot, and returns the part on the left side of the dot def stripAliasDotNumber( self, inString ): lastDotPosition = inString.rfind( "." ) if lastDotPosition > 0: return inString[0:lastDotPosition] # Return the substring before the last dot else : return None # Method to find the target tag for the alias def findTargetElm( self, tgtName ): # print "Need to find target <{0}>".format( tgtName ) # Search for elements whose path matches the name with a dot added to the end # since we never remove the last dot from the path of the tag objects # Look for match for both global and program scopes scopedTgtName = self.prefix + plcElementXML.elementSeparator + tgtName matchedTags = plcElementXML.elementSearch.search( ( tgtName + ".", scopedTgtName + "." ) ) # Will return None if not match is found for that target name if len(matchedTags) != 1: print("Number of alias Matches for <{0}> name is {1}".format( tgtName, len( matchedTags ))) return None return matchedTags[0] def __repr__(self): retString = "<{0}> ".format( self.name ) if self.scalar is not None: retString += "Has scalar tag <{0}> of type {1}".format(self.path, self.scalar.type) + "\n" return retString + plcElementXML.__repr__(self) def __str__(self): retString = "<{0}> ".format( self.name ) if self.scalar is not None: retString += "Has scalar tag <{0}> of type {1}".format(self.path, self.scalar.type) + "\n" return retString + plcElementXML.__repr__(self) #=============================================================================== # Class to handle Structure elements in the RSLogix5000 XML file #=============================================================================== class plcStructure( plcElementXML ): """Allen-Bradley PLC RSLogix5000 Tag structures""" # Constructor of the tag from XML element def __init__( self, rootElement, parentObj = None, forcedName=None ): try: plcElementXML.__init__(self, rootElement, parentObj, forcedName ) except UselessDataType as err: #print(err) return #Inherit access level # if parentObj is not None: # self.accessLevel = parentObj.accessLevel self.dataValueMembers = [] self.arrayMembers = [] self.structureMembers = [] self.daughters.append( self.dataValueMembers ) self.daughters.append( self.arrayMembers ) self.daughters.append( self.structureMembers ) # Check to see if DataType is defined if self.dataType == "": print("Structure {0} does not know its DataType".format( self.name )) sys.exit(-1) # Get all DataValueMembers. Here it is pretty straightforward dvElms = self.root.xpath( "DataValueMember" ) for dvElm in dvElms: newDataValue = plcDataValueMember( dvElm, self ) self.dataValueMembers.append( newDataValue ) # Get all ArrayMembers inside this tag amElms = self.root.xpath( "ArrayMember" ) for amElm in amElms: newArray = plcArrayMember( amElm, self ) self.arrayMembers.append(newArray) # Get all StructureMEmbers inside this tag smElms = self.root.xpath( "StructureMember" ) for smElm in smElms: newStruct = plcStructureMember(smElm, self) self.structureMembers.append( newStruct ) return # Override the structure type to return its data type def getStructureType( self ): return self.dataType #=============================================================================== # Class to handle StructureMember elements in the RSLogix5000 XML file # Pretty much identical to plcStructure class, except the name should contribute # to the path (that is the PLC tag name #=============================================================================== class plcStructureMember( plcElementXML ): """Class to handle StructureMember elements in the PLC XML file """ # Constructor of the tag from XML element def __init__( self, rootElement, parentObj = None, forcedName=None ): try: plcElementXML.__init__(self, rootElement, parentObj, forcedName ) except UselessDataType as err: #print(err) return #Inherit access level if parentObj is not None: self.accessLevel = parentObj.accessLevel self.structureType = None self.dataValueMembers = [] self.arrayMembers = [] self.structureMembers = [] self.daughters.append( self.dataValueMembers ) self.daughters.append( self.arrayMembers ) self.daughters.append( self.structureMembers ) if self.name == "": print("StructureMember of <{0}> does not have a name. Exiting...".format( self.parent.name )) # The name can contribute to the path self.addNameToPath( self.name ) # Check to see if DataType is defined if self.dataType == "": print("Structure {0} does not know its DataType".format( self.name )) sys.exit(-1) if self.dataType in plcData.atomicTypes: # This tag is PLC atomic type, it seems like it can only be scalar # Assume that this is a scalar number of atomic type. Define the scaler data # and move on dataValue = None dataRadix = "" if "Radix" in list(self.root.attrib.keys()): dataRadix = self.root.attrib["Radix"] if "Value" in list(self.root.attrib.keys()): dataValue = self.root.attrib["Value"] self.scalar = plcData.plcScalar( self.path, self.dataType, self.accessLevel, dataValue, dataRadix ) # Set the parent data type to the data type of the data (the structure is supposed to be its parent). # This will be used in case of the AddOn instructions where the access level is changed. self.scalar.structureType = parentObj.getStructureType() return # Get all DataValueMembers. Here it is pretty straightforward dvElms = self.root.xpath( "DataValueMember" ) for dvElm in dvElms: newDataValue = plcDataValueMember( dvElm, self ) self.dataValueMembers.append( newDataValue ) # Get all ArrayMembers inside this tag amElms = self.root.xpath( "ArrayMember" ) for amElm in amElms: newArray = plcArrayMember( amElm, self ) self.arrayMembers.append(newArray) # Get all StructureMembers inside this tag smElms = self.root.xpath( "StructureMember" ) for smElm in smElms: newStruct = plcStructureMember(smElm, self) self.structureMembers.append( newStruct ) return # Override the structure type to return parents structure data type def getStructureType( self ): return self.structureType