本文将尝试说明如何使用C#创建一个完整的OPC UA服务器。我们将使用开源的OPC UA .NET Standard库,感觉要写一个完整的服务还是太麻烦了,要考虑的太多了,只能给有需要的朋友一点点意见吧。
首先需要安装必要的NuGet包。在Visual Studio中,通过NuGet包管理器安装:
C#OPCFoundation.NetStandard.Opc.Ua
C#class Program {
static async Task Main(string[] args) {
MyServer server = new MyServer();
await server.Start();
Console.WriteLine("OPC UA Server started. Press Enter to exit.");
Console.ReadLine();
server.Stop();
}
}
C#public class MyServer {
private ApplicationInstance application;
private StandardServer server;
private MyNodeManager nodeManager;
public async Task Start() {
try {
// 步骤1:创建应用程序配置
application =
new ApplicationInstance { ApplicationName = "My OPC UA Server",
ApplicationType = ApplicationType.Server,
ConfigSectionName = "MyOpcUaServer" };
// 加载应用程序配置
var config = await LoadApplicationConfiguration();
// 步骤2:检查证书
await application.CheckApplicationInstanceCertificate(false, 2048);
// 步骤3:创建自定义服务器实例
server = new CustomStandardServer(config);
// 步骤4:启动服务器
await application.Start(server);
Console.WriteLine(
$"Server started at: {config.ServerConfiguration.BaseAddresses[0]}");
} catch (Exception ex) {
Console.WriteLine($"Error starting server: {ex.Message}");
throw;
}
}
public void Stop() {
if (server != null) {
server.Stop();
}
}
private async Task<ApplicationConfiguration> LoadApplicationConfiguration() {
var config = new ApplicationConfiguration() {
ApplicationName = "My OPC UA Server",
ApplicationUri = "urn:MyOpcUaServer",
ApplicationType = ApplicationType.Server,
SecurityConfiguration =
new SecurityConfiguration {
ApplicationCertificate = new CertificateIdentifier(),
TrustedPeerCertificates = new CertificateTrustList(),
TrustedIssuerCertificates = new CertificateTrustList(),
RejectedCertificateStore = new CertificateTrustList(),
AutoAcceptUntrustedCertificates = true
},
TransportConfigurations = new TransportConfigurationCollection(),
TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
ServerConfiguration =
new ServerConfiguration {
BaseAddresses =
new StringCollection {
"opc.tcp://localhost:4840/MyOpcUaServer"
},
MinRequestThreadCount = 5,
MaxRequestThreadCount = 100,
MaxQueuedRequestCount = 200,
},
TraceConfiguration = new TraceConfiguration()
};
await config.Validate(ApplicationType.Server);
return config;
}
}
// 自定义服务器类,用于初始化节点管理器
public class CustomStandardServer : StandardServer {
private ApplicationConfiguration configuration;
public CustomStandardServer(ApplicationConfiguration configuration) {
this.configuration = configuration;
}
protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration
{
List<INodeManager> nodeManagers = new List<INodeManager>();
// 创建自定义节点管理器并添加到列表
nodeManagers.Add(new MyNodeManager(server, configuration));
// 创建主节点管理器
return new MasterNodeManager(server, configuration, null,
nodeManagers.ToArray());
}
}
C#public class MyNodeManager : CustomNodeManager2
{
private IServerInternal server;
private ApplicationConfiguration configuration;
public MyNodeManager(IServerInternal server, ApplicationConfiguration configuration)
: base(server, configuration, "http://myopcuaserver.com")
{
this.server = server;
this.configuration = configuration;
SystemContext.NodeIdFactory = this;
}
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
{
lock (Lock)
{
IList<IReference> references = null;
if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out references))
{
references = new List<IReference>();
externalReferences[ObjectIds.ObjectsFolder] = references;
}
// 创建 MyDevices 文件夹
FolderState myDevicesFolder = CreateFolder(null, "MyDevices", "设备");
// 将文件夹组织到 Objects 文件夹下
myDevicesFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, myDevicesFolder.NodeId));
// 设置文件夹属性
myDevicesFolder.EventNotifier = EventNotifiers.SubscribeToEvents;
AddPredefinedNode(SystemContext, myDevicesFolder);
// 创建温度变量
BaseDataVariableState temperatureVariable = CreateVariable(
myDevicesFolder,
"Temperature",
"温度",
DataTypeIds.Double,
ValueRanks.Scalar);
// 设置温度变量的属性
temperatureVariable.Value = 25.0;
temperatureVariable.AccessLevel = AccessLevels.CurrentRead | AccessLevels.CurrentWrite;
temperatureVariable.UserAccessLevel = AccessLevels.CurrentRead | AccessLevels.CurrentWrite;
temperatureVariable.Historizing = false;
temperatureVariable.StatusCode = StatusCodes.Good;
temperatureVariable.Timestamp = DateTime.Now;
// 添加工程单位
AddEngineeringUnit(temperatureVariable, "°C", "摄氏度");
// 添加描述
temperatureVariable.Description = new LocalizedText("当前温度值");
// 添加范围
AddAnalogRange(temperatureVariable, -100.0, 100.0);
AddPredefinedNode(SystemContext, temperatureVariable);
// 创建湿度变量
BaseDataVariableState humidityVariable = CreateVariable(
myDevicesFolder,
"Humidity",
"湿度",
DataTypeIds.Double,
ValueRanks.Scalar);
// 设置湿度变量的属性
humidityVariable.Value = 60.0;
humidityVariable.AccessLevel = AccessLevels.CurrentRead | AccessLevels.CurrentWrite;
humidityVariable.UserAccessLevel = AccessLevels.CurrentRead | AccessLevels.CurrentWrite;
humidityVariable.Historizing = false;
humidityVariable.StatusCode = StatusCodes.Good;
humidityVariable.Timestamp = DateTime.Now;
// 添加工程单位
AddEngineeringUnit(humidityVariable, "%RH", "相对湿度");
// 添加描述
humidityVariable.Description = new LocalizedText("当前湿度值");
// 添加范围
AddAnalogRange(humidityVariable, 0.0, 100.0);
AddPredefinedNode(SystemContext, humidityVariable);
}
}
private FolderState CreateFolder(NodeState parent, string name, string displayName)
{
FolderState folder = new FolderState(parent);
folder.SymbolicName = name;
folder.ReferenceTypeId = ReferenceTypes.Organizes;
folder.TypeDefinitionId = ObjectTypeIds.FolderType;
folder.NodeId = new NodeId(name, NamespaceIndex);
folder.BrowseName = new QualifiedName(name, NamespaceIndex);
folder.DisplayName = new LocalizedText(displayName);
folder.WriteMask = AttributeWriteMask.None;
folder.UserWriteMask = AttributeWriteMask.None;
folder.EventNotifier = EventNotifiers.None;
if (parent != null)
{
parent.AddChild(folder);
}
return folder;
}
private BaseDataVariableState CreateVariable(
NodeState parent,
string name,
string displayName,
NodeId dataType,
int valueRank)
{
BaseDataVariableState variable = new BaseDataVariableState(parent);
variable.SymbolicName = name;
variable.ReferenceTypeId = ReferenceTypes.Organizes;
variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
variable.NodeId = new NodeId(name, NamespaceIndex);
variable.BrowseName = new QualifiedName(name, NamespaceIndex);
variable.DisplayName = new LocalizedText(displayName);
variable.WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
variable.UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
variable.DataType = dataType;
variable.ValueRank = valueRank;
variable.AccessLevel = AccessLevels.CurrentRead | AccessLevels.CurrentWrite;
variable.UserAccessLevel = AccessLevels.CurrentRead | AccessLevels.CurrentWrite;
variable.MinimumSamplingInterval = 100;
if (parent != null)
{
parent.AddChild(variable);
}
return variable;
}
private void AddEngineeringUnit(BaseVariableState variable, string unitId, string displayName)
{
ExtensionObject engineeringUnit = new ExtensionObject(
new EUInformation(
unitId, // UnitId
displayName, // DisplayName
string.Empty // Description
));
PropertyState property = new PropertyState(variable);
property.ReferenceTypeId = ReferenceTypes.HasProperty;
property.TypeDefinitionId = VariableTypeIds.PropertyType;
property.NodeId = new NodeId(variable.SymbolicName + "_EURange", NamespaceIndex);
property.BrowseName = BrowseNames.EngineeringUnits;
property.DisplayName = BrowseNames.EngineeringUnits;
property.DataType = DataTypeIds.EUInformation;
property.ValueRank = ValueRanks.Scalar;
property.AccessLevel = AccessLevels.CurrentRead;
property.UserAccessLevel = AccessLevels.CurrentRead;
property.Value = engineeringUnit;
variable.AddChild(property);
}
private void AddAnalogRange(BaseDataVariableState variable, double min, double max)
{
Range range = new Range(min, max);
ExtensionObject extensionObject = new ExtensionObject(range);
PropertyState property = new PropertyState(variable);
property.ReferenceTypeId = ReferenceTypes.HasProperty;
property.TypeDefinitionId = VariableTypeIds.PropertyType;
property.NodeId = new NodeId(variable.SymbolicName + "_Range", NamespaceIndex);
property.BrowseName = BrowseNames.EURange;
property.DisplayName = BrowseNames.EURange;
property.DataType = DataTypeIds.Range;
property.ValueRank = ValueRanks.Scalar;
property.AccessLevel = AccessLevels.CurrentRead;
property.UserAccessLevel = AccessLevels.CurrentRead;
property.Value = extensionObject;
variable.AddChild(property);
}
protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
{
NodeStateCollection predefinedNodes = new NodeStateCollection();
return predefinedNodes;
}
}
使用UaExpert 简单测试
这个示例提供了一个基础但完整的OPC UA服务器实现。它可以作为开发更复杂OPC UA应用程序的起点。根据具体需求,你可以扩展功能,添加更多的节点类型,实现数据持久化等高级特性。
本文作者:rick
本文链接:https://www.idiosoft.com/post/196
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
预览: