翼度科技»论坛 编程开发 .net 查看内容

使用C#开发OPC UA服务器

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
OPC基金会提供了OPC UA .NET标准库以及示例程序,但官方文档过于简单,光看官方文档和示例程序很难弄懂OPC UA .NET标准库怎么用,花了不少时间摸索才略微弄懂如何使用,以下记录如何从一个控制台程序开发一个OPC UA服务器。
安装Nuget包

安装OPCFoundation.NetStandard.Opc.Ua

主程序

修改Program.cs代码如下:
  1. using Opc.Ua;
  2. using Opc.Ua.Configuration;
  3. using Opc.Ua.Server;
  4. namespace SampleOpcUaServer
  5. {
  6.     internal class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             // 启动OPC UA服务器
  11.             ApplicationInstance application = new ApplicationInstance();
  12.             application.ConfigSectionName = "OpcUaServer";
  13.             application.LoadApplicationConfiguration(false).Wait();
  14.             application.CheckApplicationInstanceCertificate(false, 0).Wait();
  15.             var server = new StandardServer();
  16.             var nodeManagerFactory = new NodeManagerFactory();
  17.             server.AddNodeManager(nodeManagerFactory);
  18.             application.Start(server).Wait();
  19.             // 模拟数据
  20.             var nodeManager = nodeManagerFactory.NodeManager;
  21.             var simulationTimer = new System.Timers.Timer(1000);
  22.             var random = new Random();
  23.             simulationTimer.Elapsed += (sender, EventArgs) =>
  24.             {
  25.                 nodeManager?.UpdateValue("ns=2;s=Root_Test", random.NextInt64());
  26.             };
  27.             simulationTimer.Start();
  28.             // 输出OPC UA Endpoint
  29.             Console.WriteLine("Endpoints:");
  30.             foreach (var endpoint in server.GetEndpoints().DistinctBy(x => x.EndpointUrl))
  31.             {
  32.                 Console.WriteLine(endpoint.EndpointUrl);
  33.             }
  34.             Console.WriteLine("按Enter添加新变量");
  35.             Console.ReadLine();
  36.             // 添加新变量
  37.             nodeManager?.AddVariable("ns=2;s=Root", "Test2", (int)BuiltInType.Int16, ValueRanks.Scalar);
  38.             Console.WriteLine("已添加变量");
  39.             Console.ReadLine();
  40.         }
  41.     }
  42. }
复制代码
上述代码中:

  • ApplicationInstance是OPC UA标准库中用于配置OPC UA Server和检查证书的类。
  • application.ConfigSectionName指定了配置文件的名称,配置文件是xml文件,将会在程序文件夹查找名为OpcUaServer.Config.xml的配置文件。配置文件内容见后文。
  • application.LoadApplicationConfiguration加载前面指定的配置文件。如果不想使用配置文件,也可通过代码给application.ApplicationConfiguration赋值。
  • 有StandardServer和ReverseConnectServer两种作为OPC UA服务器的类,ReverseConnectServer派生于StandardServer,这两种类的区别未深入研究,用StandardServer可满足基本的需求。
  • OPC UA的地址空间由节点组成,简单理解节点就是提供给OPC UA客户端访问的变量和文件夹。通过server.AddNodeManager方法添加节点管理工厂类,NodeManagerFactory类定义见后文。
  • 调用application.Start(server)方法后,OPC UA Server就会开始运行,并不会阻塞代码,为了保持在控制台程序中运行,所以使用Console.ReadLine()阻塞程序。
  • nodeManager?.UpdateValue是自定义的更新OPC UA地址空间中变量值的方法。
  • nodeManager?.AddVariable在此演示动态添加一个新的变量。
OPC UA配置文件

新建OpcUaServer.Config.xml文件。

在属性中设为“始终赋值”。

内容如下:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <ApplicationConfiguration
  3.   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.   xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
  5.   xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
  6. >
  7.         <ApplicationName>Sample OPC UA Server</ApplicationName>
  8.         <ApplicationUri>urn:localhost:UA:OpcUaServer</ApplicationUri>
  9.         <ProductUri>uri:opcfoundation.org:OpcUaServer</ProductUri>
  10.         <ApplicationType>Server_0</ApplicationType>
  11.         <SecurityConfiguration>
  12.                
  13.                 <ApplicationCertificate>
  14.                         <StoreType>Directory</StoreType>
  15.                         <StorePath>%CommonApplicationData%\OPC Foundation\pki\own</StorePath>
  16.                         <SubjectName>CN=Sample Opc Ua Server, C=US, S=Arizona, O=SomeCompany, DC=localhost</SubjectName>
  17.                 </ApplicationCertificate>
  18.                
  19.                 <TrustedIssuerCertificates>
  20.                         <StoreType>Directory</StoreType>
  21.                         <StorePath>%CommonApplicationData%\OPC Foundation\pki\issuer</StorePath>
  22.                 </TrustedIssuerCertificates>
  23.                
  24.                 <TrustedPeerCertificates>
  25.                         <StoreType>Directory</StoreType>
  26.                         <StorePath>%CommonApplicationData%\OPC Foundation\pki\trusted</StorePath>
  27.                 </TrustedPeerCertificates>
  28.                
  29.                 <RejectedCertificateStore>
  30.                         <StoreType>Directory</StoreType>
  31.                         <StorePath>%CommonApplicationData%\OPC Foundation\pki\rejected</StorePath>
  32.                 </RejectedCertificateStore>
  33.         </SecurityConfiguration>
  34.         <TransportConfigurations></TransportConfigurations>
  35.         <TransportQuotas>
  36.                 <OperationTimeout>600000</OperationTimeout>
  37.                 <MaxStringLength>1048576</MaxStringLength>
  38.                 <MaxByteStringLength>1048576</MaxByteStringLength>
  39.                 <MaxArrayLength>65535</MaxArrayLength>
  40.                 <MaxMessageSize>4194304</MaxMessageSize>
  41.                 <MaxBufferSize>65535</MaxBufferSize>
  42.                 <ChannelLifetime>300000</ChannelLifetime>
  43.                 <SecurityTokenLifetime>3600000</SecurityTokenLifetime>
  44.         </TransportQuotas>
  45.         <ServerConfiguration>
  46.                 <BaseAddresses>
  47.                         <ua:String>https://localhost:62545/OpcUaServer/</ua:String>
  48.                         <ua:String>opc.tcp://localhost:62546/OpcUaServer</ua:String>
  49.                 </BaseAddresses>
  50.                 <SecurityPolicies>
  51.                         <ServerSecurityPolicy>
  52.                                 <SecurityMode>SignAndEncrypt_3</SecurityMode>
  53.                                 <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</SecurityPolicyUri>
  54.                         </ServerSecurityPolicy>
  55.                         <ServerSecurityPolicy>
  56.                                 <SecurityMode>None_1</SecurityMode>
  57.                                 <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#None</SecurityPolicyUri>
  58.                         </ServerSecurityPolicy>
  59.                         <ServerSecurityPolicy>
  60.                                 <SecurityMode>Sign_2</SecurityMode>
  61.                                 <SecurityPolicyUri></SecurityPolicyUri>
  62.                         </ServerSecurityPolicy>
  63.                         <ServerSecurityPolicy>
  64.                                 <SecurityMode>SignAndEncrypt_3</SecurityMode>
  65.                                 <SecurityPolicyUri></SecurityPolicyUri>
  66.                         </ServerSecurityPolicy>
  67.                 </SecurityPolicies>
  68.                 <UserTokenPolicies>
  69.                         <ua:UserTokenPolicy>
  70.                                 <ua:TokenType>Anonymous_0</ua:TokenType>
  71.                         </ua:UserTokenPolicy>
  72.                         <ua:UserTokenPolicy>
  73.                                 <ua:TokenType>UserName_1</ua:TokenType>
  74.                         </ua:UserTokenPolicy>
  75.                         <ua:UserTokenPolicy>
  76.                                 <ua:TokenType>Certificate_2</ua:TokenType>
  77.                         </ua:UserTokenPolicy>
  78.                        
  79.                 </UserTokenPolicies>
  80.                 <DiagnosticsEnabled>false</DiagnosticsEnabled>
  81.                 <MaxSessionCount>100</MaxSessionCount>
  82.                 <MinSessionTimeout>10000</MinSessionTimeout>
  83.                 <MaxSessionTimeout>3600000</MaxSessionTimeout>
  84.                 <MaxBrowseContinuationPoints>10</MaxBrowseContinuationPoints>
  85.                 <MaxQueryContinuationPoints>10</MaxQueryContinuationPoints>
  86.                 <MaxHistoryContinuationPoints>100</MaxHistoryContinuationPoints>
  87.                 <MaxRequestAge>600000</MaxRequestAge>
  88.                 <MinPublishingInterval>100</MinPublishingInterval>
  89.                 <MaxPublishingInterval>3600000</MaxPublishingInterval>
  90.                 <PublishingResolution>50</PublishingResolution>
  91.                 <MaxSubscriptionLifetime>3600000</MaxSubscriptionLifetime>
  92.                 <MaxMessageQueueSize>10</MaxMessageQueueSize>
  93.                 <MaxNotificationQueueSize>100</MaxNotificationQueueSize>
  94.                 <MaxNotificationsPerPublish>1000</MaxNotificationsPerPublish>
  95.                 <MinMetadataSamplingInterval>1000</MinMetadataSamplingInterval>
  96.                 <AvailableSamplingRates>
  97.                         <SamplingRateGroup>
  98.                                 <Start>5</Start>
  99.                                 <Increment>5</Increment>
  100.                                 <Count>20</Count>
  101.                         </SamplingRateGroup>
  102.                         <SamplingRateGroup>
  103.                                 <Start>100</Start>
  104.                                 <Increment>100</Increment>
  105.                                 <Count>4</Count>
  106.                         </SamplingRateGroup>
  107.                         <SamplingRateGroup>
  108.                                 <Start>500</Start>
  109.                                 <Increment>250</Increment>
  110.                                 <Count>2</Count>
  111.                         </SamplingRateGroup>
  112.                         <SamplingRateGroup>
  113.                                 <Start>1000</Start>
  114.                                 <Increment>500</Increment>
  115.                                 <Count>20</Count>
  116.                         </SamplingRateGroup>
  117.                 </AvailableSamplingRates>
  118.                 <MaxRegistrationInterval>30000</MaxRegistrationInterval>
  119.                 <NodeManagerSaveFile>OpcUaServer.nodes.xml</NodeManagerSaveFile>
  120.         </ServerConfiguration>
  121.         <TraceConfiguration>
  122.                 <OutputFilePath>Logs\SampleOpcUaServer.log</OutputFilePath>
  123.                 <DeleteOnLoad>true</DeleteOnLoad>
  124.                
  125.                
  126.                
  127.                
  128.                
  129.                 <TraceMasks>515</TraceMasks>
  130.                
  131.                
  132.                
  133.                
  134.                
  135.                
  136.         </TraceConfiguration>
  137. </ApplicationConfiguration>
复制代码
需要关注的内容有:

  • ApplicationName:在通过OPC UA工具连接此服务器时,显示的服务器名称就是该值。
  • ApplicationType:应用类型,可用的值有:

    • Server_0:服务器
    • Client_1:客户端
    • ClientAndServer_2:客户机和服务器
    • DisconveryServer_3:发现服务器。发现服务器用于注册OPC UA服务器,然后提供OPC UA客户端搜索到服务器。

  • SecurityConfiguration:该节点中指定了OPC UA的证书存储路径,一般保持默认,不需修改。
  • ServerConfiguration.BaseAddresses:该节点指定OPC UA服务器的url地址。
  • ServerConfiguration.SecurityPolicies:该节点配置允许的服务器安全策略,配置通讯是否要签名和加密。
  • ServerConfiguration.UserTokenPolicies:该节点配置允许的用户Token策略,例如是否允许匿名访问。
  • AvailableSamplingRates:配置支持的变量采样率。
  • TraceConfiguration:配置OPC UA服务器的日志记录,设定日志记录路径,配置的路径是在系统临时文件夹下的路径,日志文件的完整路径是在%TEMP%\Logs\SampleOpcUaServer.log。
NodeManagerFactory

新建NodeManagerFactory类,OPC UA server将调用该类的Create方法创建INodeManager实现类,而INodeManager实现类用于管理OPC UA地址空间。内容如下:
  1. using Opc.Ua;
  2. using Opc.Ua.Server;
  3. namespace SampleOpcUaServer
  4. {
  5.     internal class NodeManagerFactory : INodeManagerFactory
  6.     {
  7.         public NodeManager? NodeManager { get; private set; }
  8.         public StringCollection NamespacesUris => new StringCollection() { "http://opcfoundation.org/OpcUaServer" };
  9.         public INodeManager Create(IServerInternal server, ApplicationConfiguration configuration)
  10.         {
  11.             if (NodeManager != null)
  12.                 return NodeManager;
  13.             NodeManager = new NodeManager(server, configuration, NamespacesUris.ToArray());
  14.             return NodeManager;
  15.         }
  16.     }
  17. }
复制代码

  • 实现INodeManagerFactory接口,需实现NamespacesUris属性和Create方法。
  • NodeManager类是自定义的类,定义见后文。
  • 为了获取Create方法返回的NodeManager类,定义了NodeManager属性。
NodeManager

新建NodeManager类:
  1. using Opc.Ua;
  2. using Opc.Ua.Server;
  3. namespace SampleOpcUaServer
  4. {
  5.     internal class NodeManager : CustomNodeManager2
  6.     {
  7.         public NodeManager(IServerInternal server, params string[] namespaceUris)
  8.             : base(server, namespaceUris)
  9.         {
  10.         }
  11.         public NodeManager(IServerInternal server, ApplicationConfiguration configuration, params string[] namespaceUris)
  12.             : base(server, configuration, namespaceUris)
  13.         {
  14.         }
  15.         protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
  16.         {
  17.             FolderState root = CreateFolder(null, "Root");
  18.             root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder); // 将节点添加到服务器根节点
  19.             root.EventNotifier = EventNotifiers.SubscribeToEvents;
  20.             AddRootNotifier(root);
  21.             CreateVariable(root, "Test", BuiltInType.Int64, ValueRanks.Scalar);
  22.             return new NodeStateCollection(new List<NodeState> { root });
  23.         }
  24.         protected virtual FolderState CreateFolder(NodeState? parent, string name)
  25.         {
  26.             string path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;
  27.             FolderState folder = new FolderState(parent);
  28.             folder.SymbolicName = name;
  29.             folder.ReferenceTypeId = ReferenceTypes.Organizes;
  30.             folder.TypeDefinitionId = ObjectTypeIds.FolderType;
  31.             folder.NodeId = new NodeId(path, NamespaceIndex);
  32.             folder.BrowseName = new QualifiedName(path, NamespaceIndex);
  33.             folder.DisplayName = new LocalizedText("en", name);
  34.             folder.WriteMask = AttributeWriteMask.None;
  35.             folder.UserWriteMask = AttributeWriteMask.None;
  36.             folder.EventNotifier = EventNotifiers.None;
  37.             if (parent != null)
  38.             {
  39.                 parent.AddChild(folder);
  40.             }
  41.             return folder;
  42.         }
  43.         protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string name, BuiltInType dataType, int valueRank)
  44.         {
  45.             return CreateVariable(parent, name, (uint)dataType, valueRank);
  46.         }
  47.         protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string name, NodeId dataType, int valueRank)
  48.         {
  49.             string path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;
  50.             BaseDataVariableState variable = new BaseDataVariableState(parent);
  51.             variable.SymbolicName = name;
  52.             variable.ReferenceTypeId = ReferenceTypes.Organizes;
  53.             variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
  54.             variable.NodeId = new NodeId(path, NamespaceIndex);
  55.             variable.BrowseName = new QualifiedName(path, NamespaceIndex);
  56.             variable.DisplayName = new LocalizedText("en", name);
  57.             variable.WriteMask = AttributeWriteMask.None;
  58.             variable.UserWriteMask = AttributeWriteMask.None;
  59.             variable.DataType = dataType;
  60.             variable.ValueRank = valueRank;
  61.             variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
  62.             variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
  63.             variable.Historizing = false;
  64.             variable.Value = Opc.Ua.TypeInfo.GetDefaultValue(dataType, valueRank, Server.TypeTree);
  65.             variable.StatusCode = StatusCodes.Good;
  66.             variable.Timestamp = DateTime.UtcNow;
  67.             if (valueRank == ValueRanks.OneDimension)
  68.             {
  69.                 variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0 });
  70.             }
  71.             else if (valueRank == ValueRanks.TwoDimensions)
  72.             {
  73.                 variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0, 0 });
  74.             }
  75.             if (parent != null)
  76.             {
  77.                 parent.AddChild(variable);
  78.             }
  79.             return variable;
  80.         }
  81.         public void UpdateValue(NodeId nodeId, object value)
  82.         {
  83.             var variable = (BaseDataVariableState)FindPredefinedNode(nodeId, typeof(BaseDataVariableState));
  84.             if (variable != null)
  85.             {
  86.                 variable.Value = value;
  87.                 variable.Timestamp = DateTime.UtcNow;
  88.                 variable.ClearChangeMasks(SystemContext, false);
  89.             }
  90.         }
  91.                 public void AddFolder(NodeId parentId, string name)
  92.                 {
  93.                     var node = Find(parentId);
  94.                     if (node != null)
  95.                     {
  96.                         CreateFolder(node, name);
  97.                         AddPredefinedNode(SystemContext, node);
  98.                     }
  99.                 }
  100.                 public void AddVariable(NodeId parentId, string name, BuiltInType dataType, int valueRank)
  101.                 {
  102.                     AddVariable(parentId, name, (uint)dataType, valueRank);
  103.                 }
  104.                 public void AddVariable(NodeId parentId, string name, NodeId dataType, int valueRank)
  105.                 {
  106.                     var node = Find(parentId);
  107.                     if (node != null)
  108.                     {
  109.                         CreateVariable(node, name, dataType, valueRank);
  110.                         AddPredefinedNode(SystemContext, node);
  111.                     }
  112.                 }
  113.     }
  114. }
复制代码
上述代码中:

  • 需继承CustomNodeManager2,这是OPC UA标准库中提供的类。
  • 重写LoadPredefinedNodes方法,在该方法中配置预定义节点。其中创建了一个Root文件夹,Root文件夹中添加了Test变量。
  • root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder)该语句将节点添加到OPC UA服务器根节点,如果不使用该语句,可在Server节点下看到添加的节点。
  • CreateFolder是定义的方法,用于简化创建文件夹节点。
  • CreateVariable是自定义的方法,用于简化创建变量节点。
  • UpdateValue是用于更新变量节点值的方法。其中修改值后,需调用ClearChangeMasks方法,才能通知客户端更新值。
  • AddFolder用于启动服务器后添加新的文件夹。
  • AddVariable用于启动服务器后添加新的变量。
测试服务器

比较好用的测试工具有:

  • UaExpert:Unified Automation公司提供的测试工具,需安装,能用于连接OPC UA。
  • OpcExpert:opcti公司提供的免费测试工具,绿色版,能连接OPC和OPC UA。
以下用OpcExpert测试。
浏览本地计算机可发现OPC UA服务器,可看到添加的Root节点和Test变量,Test变量的值会每秒更新。

源码地址:https://github.com/Yada-Yang/SampleOpcUaServer

来源:https://www.cnblogs.com/yada/p/18257593
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具