S7 200smart 用 xml 批量部署代码
做PLC系统, 西门子是国内广泛认可的品牌, 而200smart是最具性价比的系列,
虽然开发工具很low, 各种限制, 都抵不过其广泛的认可度和低廉的价格, 更何况国内有大量兼容仿制.
200smart 在大型控制系统中, 用来做分布式控制系统, 很合适, 就是太费头发. 特别是各种通讯和映射, 特别特别是看别人的程序时, 跟踪变量来源时, 那个交叉参考...会瞎的
基于以上原因, 写了个从STL互转xml的程序. step7 mirco 可以把程序导出为 .awl 格式, 实际就是文本形式的 STL, (可能是GB2312编码)
将其转换为xml后, 就可以进行方便的 增删改查 , 从而用 python 管理整个项目的代码.
下面直接上代码, stlTxml模块, 共2个函数,
s2x : STL->XML 输入字符串, 输出 lxml.etree.Element
x2s: XML->STL 输入 lxml.etree.Element 输出 字符串

import re from lxml import etree def s2x(输入stl): 输入stl去注释 = re.sub(r'//.+?\n','',输入stl) 程序列表 = 输入stl去注释.split('\n') 块起始开头文本 = [ 'INTERRUPT_BLOCK', 'SUBROUTINE_BLOCK', 'ORGANIZATION_BLOCK' ] 程序注释起始开头文本 = [ 'TITLE=' ] 结束开头文本 = [ 'END_' ] 接口起始文本 = [ 'VAR_INPUT', 'VAR_IN_OUT', 'VAR_OUTPUT', 'VAR' ] 程序段起始文本 = [ 'BEGIN' ] 网络起始开头文本 = [ 'Network' ] 关键字字典 = { '块开始':块起始开头文本, '程序注释':程序注释起始开头文本, '结束':结束开头文本, '接口':接口起始文本, '程序段':程序段起始文本, '网络':网络起始开头文本 } def 句型判断(语句): for 句型, 关键字列表 in 关键字字典.items(): for 关键字 in 关键字列表: if re.match(关键字 + '.*?$',语句): return 句型 if re.match(r'^.+?:.+?;',语句): return '定义' return '语句' 参数枚举 = [] for i in range(16): 参数枚举.append('参数' + str(i).zfill(2)) PLC程序 = etree.Element('PLC程序') 当前标签 = None 行号 = 1 for 行 in 程序列表: if 句型判断(行) == '块开始': 分组 = re.search(r'^(?P<块类型>.+?_BLOCK)\s(?P<块名>\S+):(?P<块编号>\S+)$',行) 当前标签 = PLC程序 新标签 = etree.SubElement(当前标签,分组.group('块类型')) 当前标签 = 新标签 当前标签.set('块名',分组.group('块名')) 当前标签.set('块编号',分组.group('块编号')) elif 句型判断(行) == '程序注释': 新标签 = etree.SubElement(当前标签,'TITLE') 新标签.set('注释内容',行[6:]) elif 句型判断(行) == '接口': 新标签 = etree.SubElement(当前标签,行) 当前标签 = 新标签 行号 = 1 elif 句型判断(行) == '定义': 新标签 = etree.SubElement(当前标签,'接口变量') 分组 = re.search(r'^(?P<变量名>\S+):(?P<变量类型>\S+);$',行) 新标签.set('行号',str(行号)) 行号 += 1 for 属性名,属性 in 分组.groupdict().items(): 新标签.set(属性名,属性) elif 句型判断(行) == '结束': 当前标签 = 当前标签.getparent() elif 句型判断(行) == '程序段': 新标签 = etree.SubElement(当前标签,'BEGIN') 当前标签 = 新标签 elif 句型判断(行) == '网络': 分组 = re.search(r'^Network (?P<网络编号>\S+)\s*$',行) 网络标签 = etree.SubElement(当前标签,'Network') 网络标签.set('网络编号',分组.group('网络编号')) 行号 = 1 elif 句型判断(行) == '语句': 分组 = re.split('[,]{0,1}\s+',行) 语句标签 = etree.SubElement(网络标签,'语句') 语句标签.set('行号',str(行号)) 行号 += 1 语句标签.set('命令',分组[0]) if len(分组) > 1: for i,参数 in enumerate(分组[1:],start=1): 语句标签.set(参数枚举[i],参数) return PLC程序 def x2s(PLC程序): 输出stl = '' 光标 = 0 后缀退位栈 = [] 成对字典 = { 'ORGANIZATION_BLOCK':'END_ORGANIZATION_BLOCK', 'SUBROUTINE_BLOCK':'END_SUBROUTINE_BLOCK', 'INTERRUPT_BLOCK':'END_INTERRUPT_BLOCK', 'VAR_INPUT':'END_VAR', 'VAR_OUTPUT':'END_VAR', 'VAR':'END_VAR', 'VAR_IN_OUT':'END_VAR' } 参数枚举 = [] for i in range(16): 参数枚举.append('参数' + str(i).zfill(2)) for 块 in PLC程序.findall('./'): 前缀 = 块.tag + ' ' + 块.attrib['块名'] + ':' + 块.attrib['块编号'] + '\n' 后缀 = 成对字典[块.tag] + '\n' 输出stl = 输出stl[:光标] + 前缀 + 后缀 光标 += len(前缀) 后缀退位栈.append(len(后缀)) # 处理下一层级 for 块结构 in 块.findall('./'): if 块结构.tag == 'TITLE': 前缀 = 'TITLE=' + 块结构.attrib['注释内容'] + '\n' 输出stl = 输出stl[:光标] + 前缀 + 输出stl[光标:] 光标 += len(前缀) elif 块结构.tag in ['VAR_INPUT','VAR_OUTPUT','VAR','VAR_IN_OUT']: # 接口参数定义 前缀 = 块结构.tag + '\n' 后缀 = 成对字典[块结构.tag] + '\n' 输出stl = 输出stl[:光标] + 前缀 + 后缀 + 输出stl[光标:] 光标 += len(前缀) 后缀退位栈.append(len(后缀)) # 处理下一层级 if 后缀 == 'END_VAR\n': # 接口变量定义 变量列表 = 块结构.findall('./') 变量列表 = sorted(变量列表,key = lambda 变量: 变量.attrib['行号'].zfill(4)) for 变量 in 变量列表: 变量定义代码 = 变量.attrib['变量名'] + ':' + 变量.attrib['变量类型'] + ';\n' 输出stl = 输出stl[:光标] + 变量定义代码 + 输出stl[光标:] 光标 += len(变量定义代码) 光标 += 后缀退位栈.pop() elif 块结构.tag == 'BEGIN': # 代码区 前缀 = 'BEGIN' + '\n' 输出stl = 输出stl[:光标] + 前缀 + 输出stl[光标:] 光标 += len(前缀) # 处理下一层级 网络 for 网络 in 块结构.findall('./'): 网络代码 = 网络.tag + ' ' + 网络.attrib['网络编号'] + ' \n' 输出stl = 输出stl[:光标] + 网络代码 + 输出stl[光标:] 光标 += len(网络代码) 语句列表 = 网络.findall('./') if len(语句列表) > 0: 语句列表 = sorted(语句列表,key = lambda 变量: 变量.attrib['行号'].zfill(4)) for 语句 in 语句列表: 语句代码 = 语句.attrib['命令'].ljust(7) 参数列表 = list(语句.attrib.keys()) 参数列表 = [x for x in 参数列表 if x in 参数枚举] for i,参数 in enumerate(参数列表): if i > 0: 语句代码 += ', ' 语句代码 += 语句.attrib[参数] 语句代码 += '\n' 输出stl = 输出stl[:光标] + 语句代码 + 输出stl[光标:] 光标 += len(语句代码) 光标 += 后缀退位栈.pop() return 输出stl
可以用以下代码测试
import stlTxml from lxml import etree with open('test.txt','r',encoding='utf-8') as f: 导出程序 = f.read() 程序_xml = stlTxml.s2x(导出程序) 输出内容 = etree.tostring(程序_xml, pretty_print=True, encoding="unicode") with open(f'程序_xml.xml','w',encoding='utf-8') as f: f.write(输出内容) 程序_stl = stlTxml.x2s(程序_xml) with open('程序_stl.awl','w',encoding='utf-8') as f: f.write(程序_stl)
除了 增删改查, s2x函数相当于给STL做了 编译原理 中的 词义分析, 可以基于此做PLC仿真, 借着python的强大生态, 可以把相关工艺仿真做在一起
with enjoy