作者 | 主题 |
---|---|
zunzhi 侠圣 经验值:2714 发帖数:118 精华帖:5 |
楼主 2022-08-18 19:02:58
主题:OPC UA网关(服务)程序附完整代码 网上OPC UA服务端的介绍非常少,网关的更是找不到,找到的几乎都是收费的,最近静下心把OPC基金会的代码学习了一遍,结合一些大牛的文章,写了一个简单的OPC UA网关。目前网关的设备只支持西门子,因为只有西门子的仿真器足够强大(S7协议可以仿真),后续根据条件会加上三菱、欧姆龙等等。人过中年,总想踏踏实实做点事情,如果有好的工作平台希望大家能介绍一下,感谢! OPC是应用于工业通信的,在windows环境的下一种通讯技术,原有的通信技术难以满足日益复杂的环境,在可扩展性,安全性,跨平台性方面的不足日益明显,所以OPC基金会在几年前提出了面向未来的架构设计的OPC 统一架构,简称OPC UA,截止目前为止,越来越多公司将OPC UA作为开放的数据标准. OPC UA 基于Web服务(windows的WCF)开发的,同时又具有MQTT 可订阅的特性,部署方便、使用简单,容易被大家广泛的接受,于是随着工业4.0的发展逐渐普及 一、测试平台搭建 1.软件配置 仿真采用:S7-PLCSIM Advanced V4.0 SP1 编程采用:TIA V15.1 2.编写简单程序,提供基础变量,如布尔,整型,实数等供测试 3.仿真注意事项 1)PLC组态地址,仿真PLC地址必须和虚拟机一个网段 2)项目属性要选择块编译时支持仿真、数据块优化项去掉,通信属性中勾选允许远程PUT/GET 二.OPC UA网关架构流程 三、OPC UA 编译环境 编译软件:Visual Studio 2022 编译框架:Microsoft .NET Framework 4.6.2 四、OPC UA 网关主要部分
部分代码展示 //节点的类 public class OpcuaNode { //节点名称 public string NodeName { get; set; } //父节点 public string ParentNode { get; set; } //节点数据类型 public string DataType { get; set; } //节点类型 public string NodeType { get; set; } //节点的值 public string NodeValue { get; set; } //节点的地址 public string Adress { get; set; } } OPC UA的主要数据类型,注意和PLC变量对应 -<Aliases> <Alias Alias="Boolean">i=1</Alias> <Alias Alias="SByte">i=2</Alias> <Alias Alias="Byte">i=3</Alias> <Alias Alias="Int16">i=4</Alias> <Alias Alias="UInt16">i=5</Alias> <Alias Alias="Int32">i=6</Alias> <Alias Alias="UInt32">i=7</Alias> <Alias Alias="Int64">i=8</Alias> <Alias Alias="UInt64">i=9</Alias> <Alias Alias="Float">i=10</Alias> <Alias Alias="Double">i=11</Alias> <Alias Alias="DateTime">i=13</Alias> <Alias Alias="String">i=12</Alias> </Aliases> 读取节点配置文件加载到树形控件,同时给节点类赋值,详细见附件源码 connectPlc(); //判断PLC连接状态 if (lianjie == 1&&server==null) { //清空节点列表 opcuaNodes.Clear(); //清空列表 treeView1.Nodes.Clear(); //加载文件 doc.Load(xmlpath); //遍历节点 RecursionTreeControl1(doc.DocumentElement, treeView1.Nodes); //展开视图 treeView1.ExpandAll(); 服务启动初始化,读取服务配置,包括服务地址、端口、证书、签名或密钥的存放路径,是否启用匿名登录、是否启用非安全登录等等,详细见附件源码 //声明应用实例 ApplicationInstance application; //声明服务 ServerBase server; //声明服务配置 ApplicationConfiguration config; private void UaServerInit() { try { // Initialize the user interface. Application.EnableVisualStyles(); ApplicationInstance.MessageDlg = new ApplicationMessageDlg(); application = new ApplicationInstance(); application.ApplicationType = ApplicationType.Server; application.ConfigSectionName = "MyOPC.UA.Server"; //读取服务配置 config = application.LoadApplicationConfiguration(false).Result; //读取服务地址和端口 textBox1.Text = config.ServerConfiguration.BaseAddresses.ElementAt(1).ToString(); } catch (Exception ex) { ExceptionDlg.Show(application.ApplicationName, ex); } } PLC 变量监控分读和写2个部分,读是开一个定时器循环读取PLC的变量的值,不断刷新,写是绑定OPC UA的一个变量写入事件,写入动作时才会触发,具体见附件源码 定时读取 //CreateAddressSpace中开启定时器,读PLC m_simulationTimer = new Timer(DoSimulation, null, 1000, 1000); //定时读取PLC并更新数据 private void DoSimulation(object state) { try { lock (Lock) { var timeStamp = DateTime.UtcNow; foreach (BaseDataVariableState variable in m_dynamicNodes) { variable.Value = GetNewValue(variable); variable.Timestamp = timeStamp; variable.ClearChangeMasks(SystemContext, false); } } } catch (Exception e) { Utils.LogError(e, "Unexpected error doing simulation."); } } //读取PLC private object GetNewValue(BaseVariableState variable) { object value = null; //判断变量地址是否为空 if (variable.Des cription.Text.Length > 0) { //实数处理 //if (variable.DataType == DataTypeIds.Float) if (variable.DataType == DataTypeIds.Float variable.DataType == DataTypeIds.Double) { value = Form1.plc.Read(variable.Des cription.Text); var buffer = new byte[4]; buffer[3] = (byte)((uint)value >> 24); buffer[2] = (byte)((uint)value >> 16); buffer[1] = (byte)((uint)value >> 8); buffer[0] = (byte)((uint)value >> 0); value = (BitConverter.ToSingle(buffer, 0)).ToString(); return value; } //字符串处理 //if (variable.DataType == DataTypeIds.Float) else if (variable.DataType == DataTypeIds.String) { int tt = variable.Des cription.Text.IndexOf("."); int dbnum = int.Parse(variable.Des cription.Text.Substring(2,1)); int dbcount = int.Parse(variable.Des cription.Text.Substring(tt+4)); var count = (byte)Form1.plc.Read(DataType.DataBlock, dbnum, dbcount, VarType.Byte, 1); value = Form1.plc.Read(DataType.DataBlock, dbnum, dbcount + 1, VarType.String, count+1).ToString().Substring(1); return value; } //字符处理 else if (variable.DataType == DataTypeIds.ByteString) { value = Form1.plc.Read(variable.Des cription.Text); byte[] array = new byte[1]; array[0] = (byte)(Convert.ToInt32(value)); //ASCII码强制转换二进制 value = Convert.ToString(System.Text.Encoding.ASCII.GetString(array));//str为ASCII码对应的字符 return value; } else { //读取变量 value = Form1.plc.Read(variable.Des cription.Text); return value; } } else { value = null; } // skip Variant Null if (value is Variant variant) { if (variant.Value == null) { value = null; } } return value; } 写的事件绑定 /// <summary> /// Creates a new variable. /// </summary> private BaseDataVariableState CreateVariable(NodeState parent, string path, string name, NodeId dataType, int valueRank) {
//绑定客户端写入事件 variable.OnWriteValue = ToWritePLC;
} 具体见附件吧,已经超出字数限制了! 为了数据的安全,防止误操作,我加了一个写入保护开关,需要时开启 五、运行测试,读写正常
努力工作,好好学习!
|