2020年11月11日,OPC 基金会发布了PackML 的配套规范(OPC 30050: PackML - Packaging Control)。意味着可以使用OPCUA 信息模型来构建PackML 模型了。
如果写一篇技术简介往往是简单的,要去实现这门技术却很难。首先,OPC UA 的本文涉及的内容很多,而且不容易读懂。工业标准与IT 技术不同,网络上难以找到模板和编程技巧的介绍。多数情况下智能靠自己琢磨。也许个人的读书笔记对其他人是有帮助的。
PackML 的应用
尽管PackML 最初是为包装行业制定的标准,但是目前已经远远地超出了PackML 的范畴,它俨然成为机器的通用接口标准:基于PackML 的应用系统如下图所示
- 规范了HMI 操作界面
- 利用PackTag 实现机器之间的通信
- 利用PackTag 实现IT/OT 之间的通信
当使用OPCUA 构建 PackML 模型之后,能够使用OPC UA 的通信机制实现PackML。
应该指出,无论是PackML ,还是OPCUA 。它们都只是一种模型描述方法。它们规范了程序界面的标准化。具体的实现是需要PLC 或者机器控制器内部程序支持的。PLC 厂商提供了IEC61131-3 功能块库支持PackML状态的演变。作为工业软件的开发者,我们需要研究如何编写类似的功能块或者软件。
基于OPCUA /PackML ,我们可以开发:
- 标准化的IT 远程操作面板
- 机器操控面板
- 机器与和机器之间通过PackTag 互操作。
使用PackML 与采纳其它所有工业软件标准一样,唯一的目的是减少系统设计的工作量,实现代码复用和低代码。
机器与机器之间采用PackML 相互操作。未来还能够将PaclML 封装到AAS 资产管理壳的子管理壳中。
实现数字化制造的重点是构建物理对象的信息模型,提出的方案很多,最终我们拿什么来呈现物理对象的数字化模型?目前尚无定论。从发展趋势看,工业4.0的AAS 呼声比较高。packML也是一种方式。与此同时,OPCUA ,PackML,AAS 等模型相互交织在一起,相互引用,分分合合。搞得非常复杂。
OPCUA/PackML 的主要模型
状态机
状态机是PackML 中最重要的概念,OPC UA 模型中支持有限状态机模型,有限状态机包含了状态(state)和转移(transittion)。下图中的长方形代表状态,连线代表的是转移。转移分为两种,一种是通过外部客户端调用方法和Tag实现,另一种是内部状态完成时由内部程序完成(SC- State complete)。
packML 被OPCUA 采纳之后,导致了各种状态机模型。主要包括如下三种
- PackML基本状态机
- PackML机器状态机 是一台设备的packML 的状态机
- PackML执行状态机 是执行状态下的子状态。
PackML基本状态机
基本状态机对应packML图的Clear 阶段的状态演变(下图的灰色部分)
运行部分的状态机在内部的PackML Machine StateMachine中。
PackML 机器状态机
PackML执行状态机
在执行状态下,还可以由子状态机(subState),如果存在的话,将会在PackML执行状态机中建模。
从上面的几个状态机来看,PackML 的状态图是分层实现的:
在PackMLBaseStateMachine 中
只定义了有关Clear和Abort的状态:
- Cleared
- Aborting
- Aborted
在PackMLMachineStateMachine 中设及Reset,Stop 的状态变化
- Clearing
- Running
- Stopping
- Stoped
在 ExecuteState中
- Complete
- Competing
- Execute
- Held
- Holding
- Resetting
- Starting
- Suspened
- Suspending
- Unholding
- unsuspending
至于为什么这样嵌套式地定义各种状态和转移,不是特别明白其中的奥秘。
状态机的运行机制
PackML 设计建立在OPC UA 有限自动机模型的基础之上的。OPC UA 有限自动机信息模型如下:
有限自动机的两个最重要基本概念是状态和转移(state and Transition)。在上图中,MyMethod导致转换Transition1,Transition 1有两个引用(FromState 和ToState)。从状态1 转移到状态2.从同时产生MyEvent事件。
模型是描述出来了,MyMethod 导致Transition,transition 触发状态转移和事件触发都需要Opc UA 服务器中的程序来实现。至少Open61541 协议栈中是不包含这些程序功能的。
packML基本对象
作为一个完整的规范,PackML标准中不仅仅是状态机,还包括其它信息模型,PackML BaseObjectType是可以用于任何机器或者对象的packML 类型:
管理功能
Admin 包含了PackML OPC UA服务器的管理功能。
状态
标签
一组方法
SetParameter
SetInterLock
SetProduct
SetMatchSpeed
基本状态机
基本状态机内部包含了PackML 机器状态机(PackML Machine StateMachine)。而PackML 机器状态机内部可以包含执行状态机。
标签 PackTag
基于packML 的互操作除了采取调用Method 的方法,还有一种是采用PackTag
按文档的说法,PackTags 是命名数据元素,用于开放式架构、自动化机器中的可互操作数据交换,也可用于机器与更高级别的信息系统(如制造运营管理和企业信息系统)之间的数据交换。
PackTags 分为三组:命令(command)、状态(status)和管理(admin)。
命令标签(command)和状态标签(status)包含机器和生产线控制之间的接口以进行协调或配方/参数下载所需的数据。
- 命令标签:作为程序控制作为入口。
- 状态标签:由设备产生并修改,表示设备当前状态。
- 管理标签:包含由更高级别系统收集的用于机器性能分析或操作员信息的数据。
通常,信息数据是在基于以太网的通信网络上使用 OPC 传递的
OPC UA 中,Tag 是字符串格式的变量。在OPCUA /PackML文档中没有对PackID 有过多的说明。个人觉得OPCUA/PackML 是通过写入TagId和Status 来交换PackID。想必也需要内部程序支持。
实验过程
最好的学习方法是从做中学。为了充分理解packML OPCUA 的封装以及具体的应用,我构建了一台设备的PackML 模型。值得一提的是,OPCUA/PackML 只是一个信息模型,它背后需要相应的程序配合。国外的PLC 产品中具有功能块库支持PackML.作为工业软件的开发者,需要掌握packML 的程序设计方法。
在本实验中,
1 使用西门子公司的SiOME 或者uaModeling 软件构建一个具有PackML的OPCUA 模型。
2 使用Python 编写一个OPCUA/PackML 服务器。
3 开发一个基于OPCUA/PackML 的HMI 操控程序。
建立模型
为了充分研究PackML 的运作机理,我们建立了一个完整的PackMLBaseObjects。(我们使用的是西门子的SiOME 建模工具。
BaseStateMachine的模型
MachineState模型
ExecuteState 模型
python OPCUA/PackML Server 代码
PackML Server 要完成的主要工作包括
导入Opc.Ua.PackML_Nodeset2.XML
导入OpcUA。PackMLServer_NodeSet2.XML
设置CurrentState
实现 PackML 模型中的Method,通过Method 实现状态转移。
添加MethodCallback
在OpcUa Server 中,从NodeSet2.xml 导入模型后,如果包含了Method 对象,那就需要将一个MethodCallback 链接到MethodNode 。
在python opcua 中 使用下列函数:
server.link_method(StartMethodId,func);
在open62541 中 使用下列函数:
UA_Server_setMethodNode_callback(server, nodeId, method);
实例:
下面的程序展示了PackML 的状态变化,在客户端依次调用 start,Hold 和Unhold 方法,PackML 的currentState 依次Idle ->Execute->Held->Execute 变化。
import sys
sys.path.insert(0, "..")
import time
from opcua import ua, Serverdef StartCallback(parent,variant):#val=variant.Value#print(val[0].Value);# change Current State From Idle to Executeprint("Start Callback")path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:ExecuteState","2:Execute"]ExecuteState=objects.get_child(path)path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","0:CurrentState","0:Id"]currentStateId=objects.get_child(path)currentStateId.set_value(ExecuteState.nodeid, ua.VariantType.NodeId)return
def HoldCallback(parent):print("Hold Callback")path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:ExecuteState","2:Held"]HeldState=objects.get_child(path)path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","0:CurrentState","0:Id"]currentStateId=objects.get_child(path)currentStateId.set_value(HeldState.nodeid, ua.VariantType.NodeId)return
def UnholdCallback(parent):print("Unhold Callback")path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:ExecuteState","2:Execute"]ExecuteState=objects.get_child(path)path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","0:CurrentState","0:Id"]currentStateId=objects.get_child(path)currentStateId.set_value(ExecuteState.nodeid, ua.VariantType.NodeId)return return
if __name__ == "__main__":# setup our serverserver = Server()server.set_endpoint("opc.tcp://127.0.0.1:48400/freeopcua/server/")server.import_xml("Opc.Ua.PackML.NodeSet2.xml")server.import_xml("OpcUa.PackMLServer.NodeSet2.xml")# setup our own namespace, not really necessary but should as specuri = "http://examples.freeopcua.github.io"idx = server.register_namespace(uri)# get Objects node, this is where we should put our nodesobjects = server.get_objects_node()#path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:Stopped"]#StoppedState=objects.get_child(path)#print(StoppedState.nodeid.Identifier.numerator)path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:ExecuteState","2:Idle"]IdleState=objects.get_child(path)print(IdleState.nodeid.Identifier.numerator)path=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","0:CurrentState","0:Id"]currentStateId=objects.get_child(path)print(currentStateId.nodeid.Identifier.numerator)currentStateId.set_writable()currentStateId.set_value(IdleState.nodeid, ua.VariantType.NodeId)# Add Satrt Method Callbackpath=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:ExecuteState","2:Start"]StartMethodId=objects.get_child(path)server.link_method(StartMethodId,StartCallback)#Add Hold Method Callbackpath=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:ExecuteState","2:Hold"]HoldMethodId=objects.get_child(path)server.link_method(HoldMethodId,HoldCallback)#Add UnHold Method Callbackpath=["2:PackMLObjects", "3:TheMachine","2:BaseStateMachine","2:MachineState","2:ExecuteState","2:Unhold"]UnholdMethodId=objects.get_child(path)server.link_method(UnholdMethodId,UnholdCallback)server.load_type_definitions()# starting!server.start()try:count = 0while True:time.sleep(1)finally:#close connection, remove subcsriptions, etcserver.stop()
Python OPC UA/PackML HMI 代码
(待续)
小结
走马看花般看标准云里雾里,一旦要写代码,就要将标准看烂了方可。