{"id":19992021,"url":"https://github.com/dathlin/OpcUaHelper","last_synced_at":"2025-05-04T11:30:40.555Z","repository":{"id":38011136,"uuid":"144473045","full_name":"dathlin/OpcUaHelper","owner":"dathlin","description":"一个通用的opc ua客户端类库，基于.net 4.6.1创建，基于官方opc ua基金会跨平台库创建，封装了节点读写，批量节点读写，引用读取，特性读取，历史数据读取，方法调用，节点订阅，批量订阅等操作。还提供了一个节点浏览器工具。","archived":false,"fork":false,"pushed_at":"2025-03-27T07:47:02.000Z","size":5370,"stargazers_count":869,"open_issues_count":54,"forks_count":394,"subscribers_count":47,"default_branch":"master","last_synced_at":"2025-04-29T13:19:36.936Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dathlin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-12T14:22:03.000Z","updated_at":"2025-04-26T14:52:47.000Z","dependencies_parsed_at":"2024-06-18T13:42:11.251Z","dependency_job_id":"8702fbca-f49d-4354-bc0d-8dce0686ab79","html_url":"https://github.com/dathlin/OpcUaHelper","commit_stats":{"total_commits":29,"total_committers":4,"mean_commits":7.25,"dds":"0.10344827586206895","last_synced_commit":"46675aa4c817f2b7564e5a1ce95676e591433d59"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dathlin%2FOpcUaHelper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dathlin%2FOpcUaHelper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dathlin%2FOpcUaHelper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dathlin%2FOpcUaHelper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dathlin","download_url":"https://codeload.github.com/dathlin/OpcUaHelper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252329104,"owners_count":21730549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T04:52:00.821Z","updated_at":"2025-05-04T11:30:35.546Z","avatar_url":"https://github.com/dathlin.png","language":"C#","funding_links":[],"categories":["C\\#"],"sub_categories":[],"readme":"# OpcUaHelper\n![Build status](https://img.shields.io/badge/Build-Success-green.svg) [![NuGet Status](https://img.shields.io/nuget/v/OpcUaHelper.svg)](https://www.nuget.org/packages/OpcUaHelper/) ![NuGet Download](https://img.shields.io/nuget/dt/OpcUaHelper.svg) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](http://shang.qq.com/wpa/qunwpa?idkey=2278cb9c2e0c04fc305c43e41acff940499a34007dfca9e83a7291e726f9c4e8) [![NetFramework](https://img.shields.io/badge/Language-C%23%207.0-orange.svg)](https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/) [![Visual Studio](https://img.shields.io/badge/Visual%20Studio-2017-red.svg)](https://www.visualstudio.com/zh-hans/) ![License status](https://img.shields.io/badge/License-LGPL3.0-yellow.svg) ![copyright status](https://img.shields.io/badge/CopyRight-Richard.Hu-brightgreen.svg) \n\n一个通用的二次封装的opc ua客户端类库，基于.net 4.6.1创建，基于官方opc ua基金会跨平台库创建，方便的实现和OPC Server进行数据交互。本类库每个几个月就同步官方的类库。\n\n## 免责声明\nOpcUa相关的组件版权归OPC UA基金会所有，使用本库时请遵循OPC UA基金会的授权规则。\n1. 非商用情况，如果你的项目仅仅是自己公司使用的，那么需要注册为OPC基金会的成员，否则，必须开源。\n2. 商用都是需要额外授权的，请联系OPC基金会。\n\n## FormBrowseServer\n在开发客户端之前，需要使用本窗口来进行查看服务器的节点状态，因为在请求服务器的节点数据之前，必须知道节点的名称，而节点的名称可以通过这个窗口获取。以下演示实例化操作\n```\nOpcUaHelper.Forms.FormBrowseServer formBrowseServer = new Forms.FormBrowseServer( );\nformBrowseServer.ShowDialog( );\n```\n当然你可以固定住这个地址，传入地址即可，此处为示例：\n```\nOpcUaHelper.Forms.FormBrowseServer formBrowseServer = new Forms.FormBrowseServer( \"opc.tcp://127.0.0.1:62541/SharpNodeSettings/OpcUaServer\" );\nformBrowseServer.ShowDialog( );\n```\n界面效果如下，包含了节点的查看，订阅操作，双击值表格，还可以修改服务器的值（如果这个节点支持修改的话），查看节点的信息：\n![Picture](https://raw.githubusercontent.com/dathlin/OpcUaHelper/master/Imgs/Monitor.png)\n\n## Server Prepare\n如果你没有opc ua的服务器的话，可以参照本示例的服务器，本示例的服务器是项目 [SharpNodeSettings](https://github.com/dathlin/SharpNodeSettings) 的示例。可以直接下载这个项目运行服务器软件。\n\n或者选择在线的客户端测试: opc.tcp://118.24.36.220:62547/DataAccessServer\n\n## OpcUaClient\n实例化操作\n```\nOpcUaClient m_OpcUaClient = new OpcUaClient();\n```\n设置匿名连接\n```\nm_OpcUaClient.UserIdentity = new UserIdentity( new AnonymousIdentityToken( ) );\n```\n设置用户名连接\n```\nm_OpcUaClient.UserIdentity = new UserIdentity( \"user\", \"password\" );\n```\n使用证书连接\n```\nX509Certificate2 certificate = new X509Certificate2( \"[证书的路径]\", \"[密钥]\", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable );\nm_OpcUaClient.UserIdentity = new UserIdentity( certificate );\n```\n设置完连接的权限之后，就可以真正的启动连接操作了，连接的操作必须要放到try...catch...之前，必须使用async标记方法\n```\nprivate async void button1_Click( object sender, EventArgs e )\n{\n    // connect to server, this is a sample\n    try\n    {\n        await m_OpcUaClient.ConnectServer( \"opc.tcp://127.0.0.1:62541/SharpNodeSettings/OpcUaServer\" );\n    }\n    catch (Exception ex)\n    {\n        ClientUtils.HandleException( \"Connected Failed\", ex );\n    }\n}\n```\n### Read/Write Node\n如果我们想要读取上图节点浏览器的温度数据，节点字符串为\n```\nns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\n```\n类型为Int16, 所以我们使用下面的方法读取\n```\ntry\n{\n    short value = m_OpcUaClient.ReadNode\u003cshort\u003e( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\" );\n}\ncatch(Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n你也可以使用异步读取，只是外面的方法上需要使用async标记\n```\ntry\n{\n    short value = await m_OpcUaClient.ReadNodeAsync\u003cshort\u003e( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\" );\n}\ncatch (Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n接下来写入节点操作，如果该节点的权限不支持写的话，就会触发异常\n```\ntry\n{\n    m_OpcUaClient.WriteNode( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\", (short)123 );\n}\ncatch (Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n批量读取的操作，分为类型不一致和类型一致两种操作，下面都做个示例\n```\ntry\n{\n    // 添加所有的读取的节点，此处的示例是类型不一致的情况\n    List\u003cNodeId\u003e nodeIds = new List\u003cNodeId\u003e( );\n    nodeIds.Add( new NodeId( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\" ) );\n    nodeIds.Add( new NodeId( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/风俗\" ) );\n    nodeIds.Add( new NodeId( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速\" ) );\n    nodeIds.Add( new NodeId( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/机器人关节\" ) );\n    nodeIds.Add( new NodeId( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/cvsdf\" ) );\n    nodeIds.Add( new NodeId( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/条码\" ) );\n    nodeIds.Add( new NodeId( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/开关量\" ) );\n\n    // dataValues按顺序定义的值，每个值里面需要重新判断类型\n    List\u003cDataValue\u003e dataValues = m_OpcUaClient.ReadNodes( nodeIds.ToArray() );\n\n\n\n    // 如果你批量读取的值的类型都是一样的，比如float，那么有简便的方式\n    List\u003cstring\u003e tags = new List\u003cstring\u003e( );\n    tags.Add( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/风俗\" );\n    tags.Add( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速\" );\n\n    // 按照顺序定义的值\n    List\u003cfloat\u003e values = m_OpcUaClient.ReadNodes\u003cfloat\u003e( tags.ToArray() );\n\n}\ncatch (Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n批量写入的操作如下：\n```\ntry\n{\n    // 此处演示写入一个short，2个float类型的数据批量写入操作\n    bool success = m_OpcUaClient.WriteNodes( new string[] {\n        \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\",\n        \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/风俗\",\n        \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速\"},\n        new object[] {\n            (short)1234,\n            123.456f,\n            123f\n        } );\n    if (success)\n    {\n        // 写入成功\n    }\n    else\n    {\n        // 写入失败，一个失败即为失败\n    }\n}\ncatch (Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n\n### Read History\n```\ntry\n{\n    // 此处演示读取历史数据的操作，读取8月18日12点到13点的数据，如果想要读取成功，该节点是支持历史记录的\n    List\u003cfloat\u003e values = m_OpcUaClient.ReadHistoryRawDataValues\u003cfloat\u003e( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速\",\n        new DateTime( 2018, 8, 18, 12, 0, 0 ), new DateTime( 2018, 8, 18, 13, 0, 0 ) ).ToList( );\n    // 列表数据可用于显示曲线之类的操作\n\n}\ncatch (Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n\n### Read Attribute\n本类库支持读取一个节点的相关的所有的属性，主要包含了值，描述，名称，权限等级，等等操作\n```\ntry\n{\n    OpcNodeAttribute[] nodeAttributes = m_OpcUaClient.ReadNoteAttributes( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\" );\n    foreach (var item in nodeAttributes)\n    {\n        Console.Write( string.Format( \"{0,-30}\", item.Name ) );\n        Console.Write( string.Format( \"{0,-20}\", item.Type ) );\n        Console.Write( string.Format( \"{0,-20}\", item.StatusCode ) );\n        Console.WriteLine( string.Format( \"{0,20}\", item.Value ) );\n    }\n                \n    // 输出如下\n    //  Name                          Type                StatusCode                         Vlaue\n\n    //  NodeClass                     Int32               Good                                   2\n    //  BrowseName                    QualifiedName       Good                              2:温度\n    //  DisplayName                   LocalizedText       Good                                温度\n    //  Description                   LocalizedText       Good                                    \n    //  WriteMask                     UInt32              Good                                  96\n    //  UserWriteMask                 UInt32              Good                                  96\n    //  Value                         Int16               Good                              -11980\n    //  DataType                      NodeId              Good                                 i=4\n    //  ValueRank                     Int32               Good                                  -1\n    //  ArrayDimensions               Null                Good                                    \n    //  AccessLevel                   Byte                Good                                   3\n    //  UserAccessLevel               Byte                Good                                   3\n    //  MinimumSamplingInterval       Double              Good                                   0\n    //  Historizing                   Boolean             Good                               False\n}\ncatch (Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n\n### Read Reference\n本类库支持读取一个节点的关联节点，包含了几个简单的基本信息\n```\ntry\n{\n    ReferenceDescription[] references = m_OpcUaClient.BrowseNodeReference( \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端\" );\n    foreach (var item in references)\n    {\n        Console.Write( string.Format( \"{0,-30}\", item.NodeClass ) );\n        Console.Write( string.Format( \"{0,-30}\", item.BrowseName ) );\n        Console.Write( string.Format( \"{0,-20}\", item.DisplayName ) );\n        Console.WriteLine( string.Format( \"{0,-20}\", item.NodeId.ToString( ) ) );\n    }\n\n    ;\n    // 输出如下\n    //  NodeClass                     BrowseName                      DisplayName           NodeId\n\n    //  Variable                      2:温度                          温度                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\n    //  Variable                      2:风俗                          风俗                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/风俗\n    //  Variable                      2:转速                          转速                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/转速\n    //  Variable                      2:机器人关节                    机器人关节            ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/机器人关节\n    //  Variable                      2:cvsdf                         cvsdf                 ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/cvsdf\n    //  Variable                      2:条码                          条码                  ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/条码\n    //  Variable                      2:开关量                        开关量                ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/开关量\n}\ncatch (Exception ex)\n{\n    ClientUtils.HandleException( this.Text, ex );\n}\n```\n### Subscript\n订阅数据分为订阅单个节点和批量订阅操作，下面分别演示，本订阅的机制基于官方库进行了二次设计，方便扩展实现\n\n1. 举例说明，有个节点，**ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度** 的数据需要订阅，订阅后再界面上的 **textBox3** 上显示出来\n```\nm_OpcUaClient.AddSubscription( \"A\", \"ns=2;s=Devices/分厂一/车间二/ModbusTcp客户端/温度\", SubCallback );\n```\n这个关键字 **A** 是自己定义的，方便回调判断或是取消订阅用的，方法 **SubCallback** 是一个回调方法，代码如下：\n```\nprivate void SubCallback(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args )\n{\n    if (InvokeRequired)\n    {\n        Invoke( new Action\u003cstring, MonitoredItem, MonitoredItemNotificationEventArgs\u003e( SubCallback ), key, monitoredItem, args );\n        return;\n    }\n\n    if (key == \"A\")\n    {\n        // 如果有多个的订阅值都关联了当前的方法，可以通过key和monitoredItem来区分\n        MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;\n        if (notification != null)\n        {\n            textBox3.Text = notification.Value.WrappedValue.Value.ToString( );\n        }\n    }\n}\n```\n当服务器的值变化之后，文本框的值也会变化。如果想要取消订阅\n```\nm_OpcUaClient.RemoveSubscription( \"A\" );\n```\n\n\n2. 举例说明批量订阅，此处举例批量订阅3个点节点，按顺序在 **textBox5** , **textBox9** , **textBox10** 文本框按照顺序进行显示，此处比上面的操作需要麻烦一点，\n需要缓存下批量订阅的节点信息\n```\n// 缓存的批量订阅的节点\nprivate string[] MonitorNodeTags = null;\n\nprivate void button5_Click( object sender, EventArgs e )\n{\n    // 多个节点的订阅\n    MonitorNodeTags = new string[]\n    {\n        textBox6.Text,\n        textBox7.Text,\n        textBox8.Text,\n    };\n    m_OpcUaClient.AddSubscription( \"B\", MonitorNodeTags, SubCallback );\n}\n```\n然后修改下回调函数\n```\nprivate void SubCallback(string key, MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args )\n{\n    if (InvokeRequired)\n    {\n        Invoke( new Action\u003cstring, MonitoredItem, MonitoredItemNotificationEventArgs\u003e( SubCallback ), key, monitoredItem, args );\n        return;\n    }\n\n    if (key == \"A\")\n    {\n        // 如果有多个的订阅值都关联了当前的方法，可以通过key和monitoredItem来区分\n        MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;\n        if (notification != null)\n        {\n            textBox3.Text = notification.Value.WrappedValue.Value.ToString( );\n        }\n    }\n    else if(key == \"B\")\n    {\n        // 需要区分出来每个不同的节点信息\n        MonitoredItemNotification notification = args.NotificationValue as MonitoredItemNotification;\n        if (monitoredItem.StartNodeId.ToString( ) == MonitorNodeTags[0])\n        {\n            textBox5.Text = notification.Value.WrappedValue.Value.ToString( );\n        }\n        else if (monitoredItem.StartNodeId.ToString( ) == MonitorNodeTags[1])\n        {\n            textBox9.Text = notification.Value.WrappedValue.Value.ToString( );\n        }\n        else if (monitoredItem.StartNodeId.ToString( ) == MonitorNodeTags[2])\n        {\n            textBox10.Text = notification.Value.WrappedValue.Value.ToString( );\n        }\n    }\n}\n```\n\n\n## Thanks\n感谢使用本库，如何有任何的疑问，可以联系作者，也可以加群讨论：592132877\n\n\n## 创作不易，感谢打赏\n![Picture](https://raw.githubusercontent.com/dathlin/OpcUaHelper/master/Imgs/support.png)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdathlin%2FOpcUaHelper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdathlin%2FOpcUaHelper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdathlin%2FOpcUaHelper/lists"}