xml2st程序分析

187 阅读11分钟

1.总体结构

image.png

2.PLCControler详解

image.png

3.XML到ST的过程详解

image.png

4.生成POU程序的过程

image.png.

5.细节分析

  • 总体代码
controller = PLCControler()                                # 生成PLCControler对象
controller.OpenXMLFile(args.infilename, args.xmlstring)    # 打开XML文件
programs, errors, warnings=controller.GenerateProgram()    # 生成ST程序
  • 关键函数
class PLCControler(object):
    """
    Controler for PLCOpenEditor
    Class which controls the operations made on the plcopen model and answers to view requests
    """

    # Create a new PLCControler
    def __init__(self):
        self.LastNewIndex = 0         # 初始化一个计数器,用于生成默认的项目元素名称
        self.SortAlphaNumeric = False # 初始化一个标志位,表示是否将项目元素按字母顺序排序
        self.Reset()                  # 调用Reset()方法重置内部变量
        self.InstancesPathCollector = InstancesPathCollector(self) # 初始化InstancesPathCollector收集器对象,用于收集实例的路径
        self.POUVariablesCollector = POUVariablesCollector(self) # 初始化POUVariablesCollector收集器对象,用于收集POU的变量
        self.InstanceTagnameCollector = InstanceTagnameCollector(self) # 初始化InstanceTagnameCollector收集器对象,用于收集实例的标签名
        self.BlockInstanceCollector = BlockInstanceCollector(self) # 初始化BlockInstanceCollector收集器对象,用于收集块的实例信息
        self.VariableInfoCollector = VariableInfoCollector(self) # 初始化VariableInfoCollector收集器对象,用于收集变量信息


def GenerateProgram(self, filepath=None): # 生成PLC代码的核心方法,调用生成器类
    errors = []
    warnings = []
    # project是xml树的根节点, 可以采用 type(project) 和 dir(project)查看有那些方法
    if self.Project is not None:
        try:
            # 调用GenerateCurrentProgram,传入控制器self、项目模型和错误列表,生成PLC代码。GenerateCurrentProgram会调用ProgramGenerator进行逐步代码生成
            self.ProgramChunks = GenerateCurrentProgram(self, self.Project, errors, warnings)
            # 将当前项目模型复制到self.NextCompiledProject,用于下载代码后更新模型
            self.NextCompiledProject = self.Copy(self.Project)
            program_text = "".join([item[0] for item in self.ProgramChunks]) # 将代码片段拼接成字符串program_text
            if filepath is not None:
                programfile = open(filepath, "w")
                programfile.write(program_text.encode("utf-8"))
                programfile.close()
                self.ProgramFilePath = filepath
            return program_text, errors, warnings
        except PLCGenException as ex:
            #import traceback
            #traceback.print_exc()
            #把错误日志打印出来, 很关键
            print(ex)
            errors.append(ex)
            sys.exit(1)
    else:
        errors.append("No project opened")
    return "", errors, warnings
    
    
def GenerateCurrentProgram(controler, project, errors, warnings):
    # 创建ProgramGenerator实例generator,并传入控制器controler、项目project、错误列表errors和警告列表warnings
    generator = ProgramGenerator(controler, project, errors, warnings)
    if hasattr(controler, "logger"):
        def log(txt):
            sys.stdout.write("    "+txt+"\n")
            #controler.logger.write("    "+txt+"\n")
    else:
        def log(txt):
            sys.stdout.write("    " + txt + "\n")
            pass
    # 调用generator的GenerateProgram方法生成代码,并传入log方法,以便生成过程中输出日志
    generator.GenerateProgram(log)
    return generator.GetGeneratedProgram() # 生成完成后,调用generator的GetGeneratedProgram方法获取生成的代码

class ProgramGenerator(object):

    # Create a new PCL program generator
    def __init__(self, controler, project, errors, warnings):
        # Keep reference of the controler and project
        self.Controler = controler
        #project是xml节点的根节点
        #project有这些方法
        # GetEnumeratedDataTypeValues、getElementAttributes、getElementInfos、getLocalTag、getconfiguration、getconfigurationResource、getcontentHeader
        # getdataTypes、getfileHeader、getpous
        self.Project = project
        # Reset the internal variables used to generate PLC programs
        self.Program = []
        self.DatatypeComputed = {}
        self.PouComputed = {}
        self.Errors = errors
        self.Warnings = warnings
        
# Generate the entire program for current project
def GenerateProgram(self, log):
    #print("===Collecting data types")
    # Find all data types defined
    # 生成 project节点下的数据类型
    for datatype in self.Project.getdataTypes():
        self.DatatypeComputed[datatype.getname()] = False
    #print("===Collecting POUs")
    # Find all data types defined
    for pou in self.Project.getpous():
        self.PouComputed[pou.getname()] = False
    # Generate data type declaration structure if there is at least one data
    # type defined
    if len(self.DatatypeComputed) > 0:
        self.Program += [("TYPE\n", ())]
        # Generate every data types defined
        for datatype_name in self.DatatypeComputed.keys():
            #log("->Generate Data Type %s"%datatype_name)
            self.GenerateDataType(datatype_name)
        self.Program += [("END_TYPE\n\n", ())]
    # Generate every POUs defined
    # 生成 project下的 pou
    for pou_name in self.PouComputed.keys():
        #print("  ->Generate POU %s"%pou_name)
        self.GeneratePouProgram(pou_name)
    # Generate every configurations defined
    #print("===Generate Config(s)")
    for config in self.Project.getconfigurations():
        self.Program += self.GenerateConfiguration(config)
        

# Generate a data type from its name
def GenerateDataType(self, datatype_name):
    # Verify that data type hasn't been generated yet
    if not self.DatatypeComputed.get(datatype_name, True):
        # If not mark data type as computed
        self.DatatypeComputed[datatype_name] = True

        # Getting datatype model from project
        datatype = self.Project.getdataType(datatype_name)
        tagname = ComputeDataTypeName(datatype.getname())
        datatype_def = [("  ", ()),
                        (datatype.getname(), (tagname, "name")),
                        (" : ", ())]
        basetype_content = datatype.baseType.getcontent()
        basetype_content_type = basetype_content.getLocalTag()
        # Data type derived directly from a user defined type
        if basetype_content_type == "derived":
            basetype_name = basetype_content.getname()
            self.GenerateDataType(basetype_name)
            datatype_def += [(basetype_name, (tagname, "base"))]
        # Data type is a subrange
        elif basetype_content_type in ["subrangeSigned", "subrangeUnsigned"]:
            base_type = basetype_content.baseType.getcontent()
            base_type_type = base_type.getLocalTag()
            # Subrange derived directly from a user defined type
            if base_type_type == "derived":
                basetype_name = base_type_type.getname()
                self.GenerateDataType(basetype_name)
            # Subrange derived directly from an elementary type
            else:
                basetype_name = base_type_type
            min_value = basetype_content.range.getlower()
            max_value = basetype_content.range.getupper()
            datatype_def += [(basetype_name, (tagname, "base")),
                             (" (", ()),
                             ("%s" % min_value, (tagname, "lower")),
                             ("..", ()),
                             ("%s" % max_value, (tagname, "upper")),
                             (")", ())]
                             
# Generate a POU from its name
def GeneratePouProgram(self, pou_name):
    # Verify that POU hasn't been generated yet
    if not self.PouComputed.get(pou_name, True):
        # If not mark POU as computed
        self.PouComputed[pou_name] = True

        # Getting POU model from project
        pou = self.Project.getpou(pou_name)
        pou_type = pou.getpouType()
        # Verify that POU type exists
        if pou_type in pouTypeNames:
            # Create a POU program generator
            pou_program = PouProgramGenerator(self, pou.getname(), pouTypeNames[pou_type], self.Errors, self.Warnings)
            program = pou_program.GenerateProgram(pou) # 调用PouProgramGenerator的GenerateProgram方法生成该POU的代码program
            self.Program += program
        else:
            raise PLCGenException(_("Undefined pou type \"%s\"") % pou_type)

class PouProgramGenerator(object):
    # Create a new POU program generator
    def __init__(self, parent, name, type, errors, warnings):
        # Keep Reference to the parent generator
        self.ParentGenerator = parent
        self.Name = name
        self.Type = type
        self.TagName = ComputePouName(name)
        self.CurrentIndent = "  "
        self.ReturnType = None
        self.Interface = []
        self.InitialSteps = []
        self.ComputedBlocks = {}
        self.ComputedConnectors = {}
        self.ConnectionTypes = {}
        self.RelatedConnections = []
        self.SFCNetworks = {"Steps": {}, "Transitions": {}, "Actions": {}}
        self.SFCComputedBlocks = []
        self.ActionNumber = 0
        self.Program = []
        self.Errors = errors
        self.Warnings = warnings

def GenerateProgram(self, pou):
    # xml 中的pou节点 类型是 <class 'xmlclass.xmlclass.pou'>
    #print("\n".join(dir(pou)))
    # 获取POU的输入输出
    pouname = pou.getname()
    #print("  ------pou[%s] interface-------"%pouname)
    self.ComputeInterface(pou) # 调用ComputeInterface解析POU的接口变量
    self.ComputeConnectionTypes(pou) # 调用ComputeConnectionTypes分析POU中的连接
    #print("  ------pou[%s] body-------" % pouname)
    self.ComputeProgram(pou) # 调用ComputeProgram生成POU的主体代码

    program = [("%s " % self.Type, ()),
               (self.Name, (self.TagName, "name"))]
    if self.ReturnType is not None:
        program += [(" : ", ()),
                    (self.ReturnType, (self.TagName, "return"))]
    program += [("\n", ())]
    if len(self.Interface) == 0:
        raise PLCGenException(_("No variable defined in \"%s\" POU") % self.Name)
    if len(self.Program) == 0:
        raise PLCGenException(_("No body defined in \"%s\" POU") % self.Name)
    var_number = 0
    # 遍历接口变量,生成变量声明代码
    for list_type, option, _located, variables in self.Interface:
        variable_type = errorVarTypes.get(list_type, "var_local")
        program += [("  %s" % list_type, ())]
        if option is not None:
            program += [(" %s" % option, (self.TagName, variable_type, (var_number, var_number + len(variables)), option.lower()))]
        program += [("\n", ())]
        # 为每个变量生成声明语句,包含类型、名称、地址、初始值等
        for var_type, var_name, var_address, var_initial in variables:
            program += [("    ", ())]
            if var_name:
                program += [(var_name, (self.TagName, variable_type, var_number, "name")),
                            (" ", ())]
            if var_address is not None:
                program += [("AT ", ()),
                            (var_address, (self.TagName, variable_type, var_number, "location")),
                            (" ", ())]
            program += [(": ", ()),
                        (var_type, (self.TagName, variable_type, var_number, "type"))]
            if var_initial is not None:
                program += [(" := ", ()),
                            (self.ParentGenerator.ComputeValue(var_initial, var_type), (self.TagName, variable_type, var_number, "initial value"))]
            program += [(";\n", ())]
            var_number += 1
        program += [("  END_VAR\n", ())]
    program += [("\n", ())]
    program += self.Program       # 与POU的主体代码合并
    program += [("END_%s\n\n" % self.Type, ())]
    return program


def ComputeProgram(self, pou):
    pouname = pou.getname()
    body = pou.getbody()
    if isinstance(body, list):
        body = body[0]
    body_content = body.getcontent()
    body_type = body_content.getLocalTag()
    if body_type in ["IL", "ST"]:
        text = body_content.getanyText()
        #print(text)
        self.ParentGenerator.GeneratePouProgramInText(text.upper())
        self.Program = [(ReIndentText(text, len(self.CurrentIndent)),
                         (self.TagName, "body", len(self.CurrentIndent)))]

    ## SFC的 POU程序
    elif body_type == "SFC":
        self.IndentRight()
        for instance in body.getcontentInstances():
            if isinstance(instance, StepClass):
                self.GenerateSFCStep(instance, pou)
            elif isinstance(instance, ActionBlockClass):
                self.GenerateSFCStepActions(instance, pou)
            elif isinstance(instance, TransitionClass):
                self.GenerateSFCTransition(instance, pou)
            elif isinstance(instance, JumpStepClass):
                self.GenerateSFCJump(instance, pou)
        if len(self.InitialSteps) > 0 and len(self.SFCComputedBlocks) > 0:
            action_name = "COMPUTE_FUNCTION_BLOCKS"
            action_infos = {"qualifier": "S", "content": action_name}
            self.SFCNetworks["Steps"][self.InitialSteps[0]]["actions"].append(action_infos)
            self.SFCNetworks["Actions"][action_name] = (self.SFCComputedBlocks, ())
            self.Program = []
        self.IndentLeft()
        for initialstep in self.InitialSteps:
            self.ComputeSFCStep(initialstep)
    else:
        otherInstances = {"outVariables&coils": [], "blocks": [], "connectors": []}
        orderedInstances = []
        for instance in body.getcontentInstances():
            if isinstance(instance, (OutVariableClass, InOutVariableClass, BlockClass)):
                executionOrderId = instance.getexecutionOrderId()
                if executionOrderId > 0:
                    orderedInstances.append((executionOrderId, instance))
                elif isinstance(instance, (OutVariableClass, InOutVariableClass)):
                    otherInstances["outVariables&coils"].append(instance)
                elif isinstance(instance, BlockClass):
                    otherInstances["blocks"].append(instance)
            elif isinstance(instance, ConnectorClass):
                otherInstances["connectors"].append(instance)
            elif isinstance(instance, CoilClass):
                otherInstances["outVariables&coils"].append(instance)
        orderedInstances.sort()
        otherInstances["outVariables&coils"].sort(SortInstances)
        otherInstances["blocks"].sort(SortInstances)
        instances = [instance for (executionOrderId, instance) in orderedInstances]
        instances.extend(otherInstances["outVariables&coils"] + otherInstances["blocks"] + otherInstances["connectors"])
        for instance in instances:
            if isinstance(instance, (OutVariableClass, InOutVariableClass)):
                connections = instance.connectionPointIn.getconnections()
                if connections is not None:
                    expression = self.ComputeExpression(body, connections)
                    if expression is not None:
                        eno_var = self.GetUsedEno(body, connections)
                        if eno_var is not None:
                            self.Program += [(self.CurrentIndent + "IF %s" % eno_var, ())]
                            self.Program += [(" THEN\n  ", ())]
                            self.IndentRight()

                        self.Program += [(self.CurrentIndent, ()),
                                         (instance.getexpression(), (self.TagName, "io_variable", instance.getlocalId(), "expression")),
                                         (" := ", ())]
                        self.Program += expression
                        self.Program += [(";\n", ())]

                        if eno_var is not None:
                            self.IndentLeft()
                            self.Program += [(self.CurrentIndent + "END_IF;\n", ())]
            elif isinstance(instance, BlockClass):
                block_type = instance.gettypeName()
                self.ParentGenerator.GeneratePouProgram(block_type)
                block_infos = self.GetBlockType(block_type, tuple([self.ConnectionTypes.get(variable.connectionPointIn, "ANY") for variable in instance.inputVariables.getvariable() if variable.getformalParameter() != "EN"]))
                if block_infos is None:
                    block_infos = self.GetBlockType(block_type)
                if block_infos is None:
                    raise PLCGenException(
                        _("Undefined block type \"{a1}\" in \"{a2}\" POU").
                        format(a1=block_type, a2=self.Name))
                try:
                    self.GenerateBlock(instance, block_infos, body, None)
                except ValueError as e:
                    raise PLCGenException(str(e))
            elif isinstance(instance, ConnectorClass):
                connector = instance.getname()
                if self.ComputedConnectors.get(connector, None):
                    continue
                expression = self.ComputeExpression(body, instance.connectionPointIn.getconnections())
                if expression is not None:
                    self.ComputedConnectors[connector] = expression
            elif isinstance(instance, CoilClass):
                connections = instance.connectionPointIn.getconnections()
                if connections is not None:
                    coil_info = (self.TagName, "coil", instance.getlocalId())
                    expression = self.ComputeExpression(body, connections)
                    if expression is not None:
                        expression = self.ExtractModifier(instance, expression, coil_info)
                        self.Program += [(self.CurrentIndent, ())]
                        self.Program += [(instance.getvariable(), coil_info + ("reference",))]
                        self.Program += [(" := ", ())] + expression + [(";\n", ())]
                        
# 生成POU中SFC部分的代码
def ComputeSFCStep(self, step_name):
    if step_name in self.SFCNetworks["Steps"].keys():
        step_infos = self.SFCNetworks["Steps"].pop(step_name)
        self.Program += [(self.CurrentIndent, ())]
        if step_infos["initial"]:
            self.Program += [("INITIAL_", ())]
        self.Program += [("STEP ", ()),
                         (step_name, (self.TagName, "step", step_infos["id"], "name")),
                         (":\n", ())]
        actions = []
        self.IndentRight()
        for action_infos in step_infos["actions"]:
            if action_infos.get("id", None) is not None:
                action_info = (self.TagName, "action_block", action_infos["id"], "action", action_infos["num"])
            else:
                action_info = ()
            actions.append(action_infos["content"])
            self.Program += [(self.CurrentIndent, ()),
                             (action_infos["content"], action_info + ("reference",)),
                             ("(", ()),
                             (action_infos["qualifier"], action_info + ("qualifier",))]
            if "duration" in action_infos:
                self.Program += [(", ", ()),
                                 (action_infos["duration"], action_info + ("duration",))]
            if "indicator" in action_infos:
                self.Program += [(", ", ()),
                                 (action_infos["indicator"], action_info + ("indicator",))]
            self.Program += [(");\n", ())]
        self.IndentLeft()
        self.Program += [("%sEND_STEP\n\n" % self.CurrentIndent, ())]
        for action in actions:
            self.ComputeSFCAction(action)
        for transition in step_infos["transitions"]:
            self.ComputeSFCTransition(transition)

def ComputeSFCAction(self, action_name):
    if action_name in self.SFCNetworks["Actions"].keys():
        action_content, action_info = self.SFCNetworks["Actions"].pop(action_name)
        self.Program += [("%sACTION " % self.CurrentIndent, ()),
                         (action_name, action_info),
                         (":\n", ())]
        self.Program += action_content
        self.Program += [("%sEND_ACTION\n\n" % self.CurrentIndent, ())]

def ComputeSFCTransition(self, transition):
    if transition in self.SFCNetworks["Transitions"].keys():
        transition_infos = self.SFCNetworks["Transitions"].pop(transition)
        self.Program += [("%sTRANSITION" % self.CurrentIndent, ())]
        if transition_infos["priority"] is not None:
            self.Program += [(" (PRIORITY := ", ()),
                             ("%d" % transition_infos["priority"], (self.TagName, "transition", transition_infos["id"], "priority")),
                             (")", ())]
        self.Program += [(" FROM ", ())]
        if len(transition_infos["from"]) > 1:
            self.Program += [("(", ())]
            self.Program += JoinList([(", ", ())], transition_infos["from"])
            self.Program += [(")", ())]
        elif len(transition_infos["from"]) == 1:
            self.Program += transition_infos["from"][0]
        else:
            raise PLCGenException(
                _("Transition with content \"{a1}\" not connected to a previous step in \"{a2}\" POU").
                format(a1=transition_infos["content"], a2=self.Name))
        self.Program += [(" TO ", ())]
        if len(transition_infos["to"]) > 1:
            self.Program += [("(", ())]
            self.Program += JoinList([(", ", ())], transition_infos["to"])
            self.Program += [(")", ())]
        elif len(transition_infos["to"]) == 1:
            self.Program += transition_infos["to"][0]
        else:
            raise PLCGenException(
                _("Transition with content \"{a1}\" not connected to a next step in \"{a2}\" POU").
                format(a1=transition_infos["content"], a2=self.Name))
        self.Program += transition_infos["content"]
        self.Program += [("%sEND_TRANSITION\n\n" % self.CurrentIndent, ())]
        for [(step_name, _step_infos)] in transition_infos["to"]:
            self.ComputeSFCStep(step_name)


def GetBlockType(self, type, inputs=None):
    return self.ParentGenerator.Controler.GetBlockType(type, inputs)
        
        
* * * * * * * * * * * * * * * * * *
    
# self.ProgramChunks的值
[('PROGRAM ', ()), ('PLC_PRG', ('P::PLC_PRG', 'name')), ('\n', ()), ('  VAR', ()), (' RETAIN', ('P::PLC_PRG', 'var_local', (0, 1), 'retain')), ('\n', ()), ('    ', ()), ('local1', ('P::PLC_PRG', 'var_local', 0, 'name')), (' ', ()), (': ', ()), (u'BOOL', ('P::PLC_PRG', 'var_local', 0, 'type')), (' := ', ()), ('true', ('P::PLC_PRG', 'var_local', 0, 'initial value')), (';\n', ()), ('  END_VAR\n', ()), ('  VAR', ()), ('\n', ()), ('    ', ()), ('local2', ('P::PLC_PRG', 'var_local', 1, 'name')), (' ', ()), (': ', ()), (u'BOOL', ('P::PLC_PRG', 'var_local', 1, 'type')), (' := ', ()), ('true', ('P::PLC_PRG', 'var_local', 1, 'initial value')), (';\n', ()), ('  END_VAR\n', ()), ('\n', ()), ('  local2 := TRUE;\n      local1 := FALSE;\n', ('P::PLC_PRG', 'body', 2)), ('END_PROGRAM\n\n', ()), ('\nCONFIGURATION ', ()), ('Config0', ('C::Config0', 'name')), ('\n', ()), ('\n  RESOURCE ', ()), ('Res0', ('R::Config0::Res0', 'name')), (' ON PLC\n', ()), ('    TASK ', ()), ('task0', ('R::Config0::Res0', 'task', 0, 'name')), ('(', ()), ('INTERVAL := ', ()), ('T#20ms', ('R::Config0::Res0', 'task', 0, 'interval')), (',', ()), ('PRIORITY := ', ()), ('0', ('R::Config0::Res0', 'task', 0, 'priority')), (');\n', ()), ('    PROGRAM ', ()), ('instance0', ('R::Config0::Res0', 'instance', 0, 'name')), (' WITH ', ()), ('task0', ('R::Config0::Res0', 'instance', 0, 'task')), (' : ', ()), ('PLC_PRG', ('R::Config0::Res0', 'instance', 0, 'type')), (';\n', ()), ('  END_RESOURCE\n', ()), ('END_CONFIGURATION\n', ())]

# POU生成的SFC主体代码self.Program的值
[('  ', ()), ('INITIAL_', ()), ('STEP ', ()), ('Init', ('P::aaaa', 'step', 0, 'name')), (':\n', ()), ('  END_STEP\n\n', ()), ('  TRANSITION', ()), (' (PRIORITY := ', ()), ('0', ('P::aaaa', 'transition', 3, 'priority')), (')', ()), (' FROM ', ()), ('Init', ('P::aaaa', 'transition', 3, 'from', 0)), (' TO ', ()), ('Step0', ('P::aaaa', 'transition', 3, 'to', 4)), ('\n    := ', ()), ('a', ('P::aaaa', 'io_variable', 2, 'expression')), (';\n', ()), ('  END_TRANSITION\n\n', ()), ('  ', ()), ('STEP ', ()), ('Step0', ('P::aaaa', 'step', 4, 'name')), (':\n', ()), ('  END_STEP\n\n', ()), ('  TRANSITION', ()), (' (PRIORITY := ', ()), ('0', ('P::aaaa', 'transition', 7, 'priority')), (')', ()), (' FROM ', ()), ('Step0', ('P::aaaa', 'transition', 7, 'from', 4)), (' TO ', ()), ('Init', ('P::aaaa', 'jump', 8, 'target')), ('\n    := ', ()), ('a', ('P::aaaa', 'io_variable', 6, 'expression')), (';\n', ()), ('  END_TRANSITION\n\n', ())]

# program_text的值
PROGRAM PLC_PRG
  VAR RETAIN
    local1 : BOOL := true;
  END_VAR
  VAR
    local2 : BOOL := true;
  END_VAR

  local2 := TRUE;
      local1 := FALSE;
END_PROGRAM


CONFIGURATION Config0

  RESOURCE Res0 ON PLC
    TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
    PROGRAM instance0 WITH task0 : PLC_PRG;
  END_RESOURCE
END_CONFIGURATION

6.代码运行

python2 PLCControler.py -i /home/zwx/Program/xml2st/plc.xml -o zwxhhh.st

python2 -m pdb  PLCControler.py -i /home/zwx/Program/xml2st/plc.xml

7.代码错误提示

  1. PLCGenerator.py中定义了PLCGenException类,用于抛出PLC代码生成过程中的异常。 当代码生成过程中遇到错误时,可以raise PLCGenException来抛出异常并中断代码生成过程
  2. 在PouProgramGenerator的GenerateBlock方法中,会根据函数块的输入和输出变量类型进行检查,如果不匹配会抛出ValueError异常
  3. 在GenerateSFCStep等方法中,会检查SFC图形元素的连接关系是否正确,若不正确会抛出PLCGenException异常
  4. 在GenerateConfiguration等方法中,也会根据配置要求进行检查,如资源或全局变量定义时类型不匹配等会抛出异常
  5. 在GenerateProgram方法中,会检查项目元素是否符合要求,如POU没有输入变量、代码体为空等情况会抛出PLCGenException异常
  6. PLCControler和ProgramGenerator都持有errors和warnings列表,代码生成过程中的非严重问题会添加到 warnings,严重错误添加到errors。

8.标准xml文件与对应的st文件

image.png

image.png

image.png

9.xml文件细节解释

<body>
    <SFC>
        <step localId="0" name="Init" initialStep="true">
            <position x="50" y="50"/>   <!-- 指定此步骤在SFC图上的坐标位置 -->
            <connectionPointIn>         <!-- 定义了步骤的输入连接点 -->
                <connection formalParameter="sfc"/>  <!-- 表示此连接点用于SFC连接 -->
            </connectionPointIn>
            <connectionPointOut formalParameter="sfc"/>   <!-- 定义了步骤的输出连接点 -->
        </step>
        <inVariable localId="2">
            <connectionPointOut/>    <!-- 表示这个变量有一个输出连接点 -->
            <expression>a</expression>  <!-- 表示该变量实际绑定到程序变量a -->
        </inVariable>
        <transition localId="3" priority="0">
            <connectionPointIn>
                <connection refLocalId="0" formalParameter="sfc"/>  <!-- 引用了localId
为0的输出作为转换的输入 -->
            </connectionPointIn>
            <connectionPointOut/>   <!-- 表示这个变量有一个输出连接点 -->
            <condition>             <!-- 检查localId为2的变量a作为转换的条件 -->
                <connectionPointIn>
                    <connection refLocalId="2"></connection>
                </connectionPointIn>
            </condition>
        </transition>
    </SFC>
</body>

10.SFC错误情况

检查SFC中是否缺少transition

image.png

在SFC中不缺少transition的情况下,判断transition是否多余或者缺少step

image.png 文字描述:

  • 存在多个init初始步
  • step重名
  • step前缺少transition
  • step之间存在多个Transition
  • 并行分支
    • 发散节点前缺少transition
    • 发散节点后存在transition
    • 收敛节点前存在trans
    • 收敛节点后缺少trans
  • 选择分支
    • 发散节点前存在trans
    • 发散节点后缺少trans
    • 收敛节点前缺少trans
    • 收敛节点后存在trans
  • Jump
    • jump前节点是步
  • 并行分支嵌套选择分支

11.任务配置检查

image.png

image.png

12.主要修改部分

def GenerateSFCTransition(self, transition, pou):
    if transition not in self.SFCNetworks["Transitions"].keys():
        steps = []
        connections = transition.connectionPointIn.getconnections()
        if connections is not None and len(connections) == 1:
            instanceLocalId = connections[0].getrefLocalId()
            body = pou.getbody()
            if isinstance(body, list):
                body = body[0]
            instance = body.getcontentInstance(instanceLocalId)
            if isinstance(instance, StepClass):
                steps.append(instance)
            elif isinstance(instance, SelectionDivergenceClass):
                step = self.ExtractDivergenceInput(instance, pou)
                if step is not None:
                    if isinstance(step, StepClass):
                        steps.append(step)
                    elif isinstance(step, SimultaneousConvergenceClass):
                        steps.extend(self.ExtractStepOfConvergenceInputs(step, pou))
                    elif isinstance(step, SimultaneousDivergenceClass):
                        steps.extend(self.ExtractDivergenceInput(step, pou))
            elif isinstance(instance, SimultaneousConvergenceClass):
                steps.extend(self.ExtractStepOfConvergenceInputs(instance, pou))
def ExtractStepOfConvergenceInputs(self, convergence, pou):
    instances = []
    for connectionPointIn in convergence.getconnectionPointIn():
        connections = connectionPointIn.getconnections()
        if connections is not None and len(connections) == 1:
            instanceLocalId = connections[0].getrefLocalId()
            body = pou.getbody()
            if isinstance(body, list):
                body = body[0]
            instance = body.getcontentInstance(instanceLocalId)
            if isinstance(instance, StepClass):
                instances.append(body.getcontentInstance(instanceLocalId))

    return instances

def ExtractStepOfDivergenceInputs(self, divergence, pou):
    instances = []
    for connectionPointIn in divergence.getconnectionPointIn():
        connections = connectionPointIn.getconnections()
        if connections is not None and len(connections) == 1:
            instanceLocalId = connections[0].getrefLocalId()
            body = pou.getbody()
            if isinstance(body, list):
                body = body[0]
            instance = body.getcontentInstance(instanceLocalId)
            if isinstance(instance, StepClass):
                instances.append(instance)
            elif isinstance(instance, SimultaneousDivergenceClass):
                instances.append(ExtractStepOfDivergenceInputs(instance, pou))

    return instances

12.每个节点的情况

  1. 转移
  2. 选择分支发散
  3. 选择分支收敛
  4. 并行分支发散
  5. 并行分支收敛
  6. 跳转

步之前可以的节点

  • 转移
  • 选择分支收敛
  • 并行分支发散

转移之前可以的节点

  • 并行分支收敛
  • 选择分支发散

选择分支发散之前可以的节点

  • 并行分支收敛
  • 选择分支发散

选择分支收敛之前可以的节点

  • 转移

并行分支发散之前可以的节点

  • 转移
  • 选择分支收敛
  • 并行分支发散

并行分支收敛之前可以的节点

跳转之前可以的节点

  • 转移
  • 选择分支收敛
  • 并行分支发散