|
OPC基金会提供了OPC UA .NET标准库以及示例程序,但官方文档过于简单,光看官方文档和示例程序很难弄懂OPC UA .NET标准库怎么用,花了不少时间摸索才略微弄懂如何使用,以下记录如何从一个控制台程序开发一个OPC UA服务器。
安装Nuget包
安装OPCFoundation.NetStandard.Opc.Ua
主程序
修改Program.cs代码如下:- using Opc.Ua;
- using Opc.Ua.Configuration;
- using Opc.Ua.Server;
- namespace SampleOpcUaServer
- {
- internal class Program
- {
- static void Main(string[] args)
- {
- // 启动OPC UA服务器
- ApplicationInstance application = new ApplicationInstance();
- application.ConfigSectionName = "OpcUaServer";
- application.LoadApplicationConfiguration(false).Wait();
- application.CheckApplicationInstanceCertificate(false, 0).Wait();
- var server = new StandardServer();
- var nodeManagerFactory = new NodeManagerFactory();
- server.AddNodeManager(nodeManagerFactory);
- application.Start(server).Wait();
- // 模拟数据
- var nodeManager = nodeManagerFactory.NodeManager;
- var simulationTimer = new System.Timers.Timer(1000);
- var random = new Random();
- simulationTimer.Elapsed += (sender, EventArgs) =>
- {
- nodeManager?.UpdateValue("ns=2;s=Root_Test", random.NextInt64());
- };
- simulationTimer.Start();
- // 输出OPC UA Endpoint
- Console.WriteLine("Endpoints:");
- foreach (var endpoint in server.GetEndpoints().DistinctBy(x => x.EndpointUrl))
- {
- Console.WriteLine(endpoint.EndpointUrl);
- }
- Console.WriteLine("按Enter添加新变量");
- Console.ReadLine();
- // 添加新变量
- nodeManager?.AddVariable("ns=2;s=Root", "Test2", (int)BuiltInType.Int16, ValueRanks.Scalar);
- Console.WriteLine("已添加变量");
- Console.ReadLine();
- }
- }
- }
复制代码 上述代码中:
- 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文件。
在属性中设为“始终赋值”。
内容如下:需要关注的内容有:
- 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地址空间。内容如下:- using Opc.Ua;
- using Opc.Ua.Server;
- namespace SampleOpcUaServer
- {
- internal class NodeManagerFactory : INodeManagerFactory
- {
- public NodeManager? NodeManager { get; private set; }
- public StringCollection NamespacesUris => new StringCollection() { "http://opcfoundation.org/OpcUaServer" };
- public INodeManager Create(IServerInternal server, ApplicationConfiguration configuration)
- {
- if (NodeManager != null)
- return NodeManager;
- NodeManager = new NodeManager(server, configuration, NamespacesUris.ToArray());
- return NodeManager;
- }
- }
- }
复制代码
- 实现INodeManagerFactory接口,需实现NamespacesUris属性和Create方法。
- NodeManager类是自定义的类,定义见后文。
- 为了获取Create方法返回的NodeManager类,定义了NodeManager属性。
NodeManager
新建NodeManager类:- using Opc.Ua;
- using Opc.Ua.Server;
- namespace SampleOpcUaServer
- {
- internal class NodeManager : CustomNodeManager2
- {
- public NodeManager(IServerInternal server, params string[] namespaceUris)
- : base(server, namespaceUris)
- {
- }
- public NodeManager(IServerInternal server, ApplicationConfiguration configuration, params string[] namespaceUris)
- : base(server, configuration, namespaceUris)
- {
- }
- protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
- {
- FolderState root = CreateFolder(null, "Root");
- root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder); // 将节点添加到服务器根节点
- root.EventNotifier = EventNotifiers.SubscribeToEvents;
- AddRootNotifier(root);
- CreateVariable(root, "Test", BuiltInType.Int64, ValueRanks.Scalar);
- return new NodeStateCollection(new List<NodeState> { root });
- }
- protected virtual FolderState CreateFolder(NodeState? parent, string name)
- {
- string path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;
- FolderState folder = new FolderState(parent);
- folder.SymbolicName = name;
- folder.ReferenceTypeId = ReferenceTypes.Organizes;
- folder.TypeDefinitionId = ObjectTypeIds.FolderType;
- folder.NodeId = new NodeId(path, NamespaceIndex);
- folder.BrowseName = new QualifiedName(path, NamespaceIndex);
- folder.DisplayName = new LocalizedText("en", name);
- folder.WriteMask = AttributeWriteMask.None;
- folder.UserWriteMask = AttributeWriteMask.None;
- folder.EventNotifier = EventNotifiers.None;
- if (parent != null)
- {
- parent.AddChild(folder);
- }
- return folder;
- }
- protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string name, BuiltInType dataType, int valueRank)
- {
- return CreateVariable(parent, name, (uint)dataType, valueRank);
- }
- protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string name, NodeId dataType, int valueRank)
- {
- string path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;
- BaseDataVariableState variable = new BaseDataVariableState(parent);
- variable.SymbolicName = name;
- variable.ReferenceTypeId = ReferenceTypes.Organizes;
- variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
- variable.NodeId = new NodeId(path, NamespaceIndex);
- variable.BrowseName = new QualifiedName(path, NamespaceIndex);
- variable.DisplayName = new LocalizedText("en", name);
- variable.WriteMask = AttributeWriteMask.None;
- variable.UserWriteMask = AttributeWriteMask.None;
- variable.DataType = dataType;
- variable.ValueRank = valueRank;
- variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
- variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
- variable.Historizing = false;
- variable.Value = Opc.Ua.TypeInfo.GetDefaultValue(dataType, valueRank, Server.TypeTree);
- variable.StatusCode = StatusCodes.Good;
- variable.Timestamp = DateTime.UtcNow;
- if (valueRank == ValueRanks.OneDimension)
- {
- variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0 });
- }
- else if (valueRank == ValueRanks.TwoDimensions)
- {
- variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0, 0 });
- }
- if (parent != null)
- {
- parent.AddChild(variable);
- }
- return variable;
- }
- public void UpdateValue(NodeId nodeId, object value)
- {
- var variable = (BaseDataVariableState)FindPredefinedNode(nodeId, typeof(BaseDataVariableState));
- if (variable != null)
- {
- variable.Value = value;
- variable.Timestamp = DateTime.UtcNow;
- variable.ClearChangeMasks(SystemContext, false);
- }
- }
- public void AddFolder(NodeId parentId, string name)
- {
- var node = Find(parentId);
- if (node != null)
- {
- CreateFolder(node, name);
- AddPredefinedNode(SystemContext, node);
- }
- }
- public void AddVariable(NodeId parentId, string name, BuiltInType dataType, int valueRank)
- {
- AddVariable(parentId, name, (uint)dataType, valueRank);
- }
- public void AddVariable(NodeId parentId, string name, NodeId dataType, int valueRank)
- {
- var node = Find(parentId);
- if (node != null)
- {
- CreateVariable(node, name, dataType, valueRank);
- AddPredefinedNode(SystemContext, node);
- }
- }
- }
- }
复制代码 上述代码中:
- 需继承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
|