iot-edge-opc-publisher-v1/opcpublisher/PublisherNodeManager.cs

657 строки
30 KiB
C#

using Opc.Ua;
using Opc.Ua.Server;
using System;
using System.Collections.Generic;
namespace OpcPublisher
{
using Newtonsoft.Json;
using System.Linq;
using System.Net;
using static OpcApplicationConfiguration;
using static OpcPublisher.Program;
using static PublisherNodeConfiguration;
public class PublisherNodeManager : CustomNodeManager2
{
public PublisherNodeManager(Opc.Ua.Server.IServerInternal server, ApplicationConfiguration configuration)
: base(server, configuration, Namespaces.PublisherApplications)
{
SystemContext.NodeIdFactory = this;
}
/// <summary>
/// Creates the NodeId for the specified node.
/// </summary>
public override NodeId New(ISystemContext context, NodeState node)
{
BaseInstanceState instance = node as BaseInstanceState;
if (instance != null && instance.Parent != null)
{
string id = instance.Parent.NodeId.Identifier as string;
if (id != null)
{
return new NodeId(id + "_" + instance.SymbolicName, instance.Parent.NodeId.NamespaceIndex);
}
}
return node.NodeId;
}
/// <summary>
/// Creates a new folder.
/// </summary>
private FolderState CreateFolder(NodeState parent, string path, string name)
{
FolderState folder = new FolderState(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypes.Organizes,
TypeDefinitionId = ObjectTypeIds.FolderType,
NodeId = new NodeId(path, NamespaceIndex),
BrowseName = new QualifiedName(path, NamespaceIndex),
DisplayName = new LocalizedText("en", name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None,
EventNotifier = EventNotifiers.None
};
if (parent != null)
{
parent.AddChild(folder);
}
return folder;
}
/// <summary>
/// Does any initialization required before the address space can be used.
/// </summary>
/// <remarks>
/// The externalReferences is an out parameter that allows the node manager to link to nodes
/// in other node managers. For example, the 'Objects' node is managed by the CoreNodeManager and
/// should have a reference to the root folder node(s) exposed by this node manager.
/// </remarks>
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
{
lock (Lock)
{
IList<IReference> references = null;
if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out references))
{
externalReferences[ObjectIds.ObjectsFolder] = references = new List<IReference>();
}
FolderState root = CreateFolder(null, "OpcPublisher", "OpcPublisher");
root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, root.NodeId));
root.EventNotifier = EventNotifiers.SubscribeToEvents;
AddRootNotifier(root);
List<BaseDataVariableState> variables = new List<BaseDataVariableState>();
try
{
FolderState dataFolder = CreateFolder(root, "Data", "Data");
const string connectionStringItemName = "ConnectionString";
DataItemState item = CreateDataItemVariable(dataFolder, connectionStringItemName, connectionStringItemName, BuiltInType.String, ValueRanks.Scalar, AccessLevels.CurrentWrite);
item.Value = String.Empty;
FolderState methodsFolder = CreateFolder(root, "Methods", "Methods");
MethodState publishNodeMethod = CreateMethod(methodsFolder, "PublishNode", "PublishNode");
SetPublishNodeMethodProperties(ref publishNodeMethod);
MethodState unpublishNodeMethod = CreateMethod(methodsFolder, "UnpublishNode", "UnpublishNode");
SetUnpublishNodeMethodProperties(ref unpublishNodeMethod);
MethodState getPublishedNodesLegacyMethod = CreateMethod(methodsFolder, "GetPublishedNodes", "GetPublishedNodes");
SetGetPublishedNodesLegacyMethodProperties(ref getPublishedNodesLegacyMethod);
}
catch (Exception e)
{
Utils.Trace(e, "Error creating the address space.");
}
AddPredefinedNode(SystemContext, root);
}
}
/// <summary>
/// Sets properties of the PublishNode method.
/// </summary>
private void SetPublishNodeMethodProperties(ref MethodState method)
{
// define input arguments
method.InputArguments = new PropertyState<Argument[]>(method)
{
NodeId = new NodeId(method.BrowseName.Name + "InArgs", NamespaceIndex),
BrowseName = BrowseNames.InputArguments
};
method.InputArguments.DisplayName = method.InputArguments.BrowseName.Name;
method.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
method.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
method.InputArguments.DataType = DataTypeIds.Argument;
method.InputArguments.ValueRank = ValueRanks.OneDimension;
method.InputArguments.Value = new Argument[]
{
new Argument() { Name = "NodeId", Description = "NodeId of the node to publish in NodeId format.", DataType = DataTypeIds.String, ValueRank = ValueRanks.Scalar },
new Argument() { Name = "EndpointUrl", Description = "Endpoint URI of the OPC UA server owning the node.", DataType = DataTypeIds.String, ValueRank = ValueRanks.Scalar }
};
method.OnCallMethod = new GenericMethodCalledEventHandler(OnPublishNodeCall);
}
/// <summary>
/// Sets properies of the UnpublishNode method.
/// </summary>
private void SetUnpublishNodeMethodProperties(ref MethodState method)
{
// define input arguments
method.InputArguments = new PropertyState<Argument[]>(method)
{
NodeId = new NodeId(method.BrowseName.Name + "InArgs", NamespaceIndex),
BrowseName = BrowseNames.InputArguments
};
method.InputArguments.DisplayName = method.InputArguments.BrowseName.Name;
method.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
method.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
method.InputArguments.DataType = DataTypeIds.Argument;
method.InputArguments.ValueRank = ValueRanks.OneDimension;
method.InputArguments.Value = new Argument[]
{
new Argument() { Name = "NodeId", Description = "NodeId of the node to publish in NodeId format.", DataType = DataTypeIds.String, ValueRank = ValueRanks.Scalar },
new Argument() { Name = "EndpointUrl", Description = "Endpoint URI of the OPC UA server owning the node.", DataType = DataTypeIds.String, ValueRank = ValueRanks.Scalar },
};
method.OnCallMethod = new GenericMethodCalledEventHandler(OnUnpublishNodeCall);
}
/// <summary>
/// Sets properies of the GetPublishedNodes method, which is only there for backward compatibility.
/// This method is acutally returning the configured nodes in NodeId syntax.
/// </summary>
private void SetGetPublishedNodesLegacyMethodProperties(ref MethodState method)
{
// define input arguments
method.InputArguments = new PropertyState<Argument[]>(method)
{
NodeId = new NodeId(method.BrowseName.Name + "InArgs", NamespaceIndex),
BrowseName = BrowseNames.InputArguments
};
method.InputArguments.DisplayName = method.InputArguments.BrowseName.Name;
method.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
method.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
method.InputArguments.DataType = DataTypeIds.Argument;
method.InputArguments.ValueRank = ValueRanks.OneDimension;
method.InputArguments.Value = new Argument[]
{
new Argument() { Name = "EndpointUrl", Description = "Endpoint URI of the OPC UA server to return the published nodes for.", DataType = DataTypeIds.String, ValueRank = ValueRanks.Scalar }
};
// set output arguments
method.OutputArguments = new PropertyState<Argument[]>(method)
{
NodeId = new NodeId(method.BrowseName.Name + "OutArgs", NamespaceIndex),
BrowseName = BrowseNames.OutputArguments
};
method.OutputArguments.DisplayName = method.OutputArguments.BrowseName.Name;
method.OutputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
method.OutputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
method.OutputArguments.DataType = DataTypeIds.Argument;
method.OutputArguments.ValueRank = ValueRanks.OneDimension;
method.OutputArguments.Value = new Argument[]
{
new Argument() { Name = "Published nodes", Description = "List of the nodes configured to publish in OPC Publisher in NodeId format", DataType = DataTypeIds.String, ValueRank = ValueRanks.Scalar }
};
method.OnCallMethod = new GenericMethodCalledEventHandler(OnGetPublishedNodesLegacyCall);
}
/// <summary>
/// Creates a new variable.
/// </summary>
private DataItemState CreateDataItemVariable(NodeState parent, string path, string name, BuiltInType dataType, int valueRank, byte accessLevel)
{
DataItemState variable = new DataItemState(parent);
variable.ValuePrecision = new PropertyState<double>(variable);
variable.Definition = new PropertyState<string>(variable);
variable.Create(
SystemContext,
null,
variable.BrowseName,
null,
true);
variable.SymbolicName = name;
variable.ReferenceTypeId = ReferenceTypes.Organizes;
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 = (uint)dataType;
variable.ValueRank = valueRank;
variable.AccessLevel = accessLevel;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = Opc.Ua.TypeInfo.GetDefaultValue((uint)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 });
}
variable.ValuePrecision.Value = 2;
variable.ValuePrecision.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.ValuePrecision.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Definition.Value = String.Empty;
variable.Definition.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Definition.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
if (parent != null)
{
parent.AddChild(variable);
}
return variable;
}
/// <summary>
/// Creates a new variable using type Numeric as NodeId.
/// </summary>
private DataItemState CreateDataItemVariable(NodeState parent, uint id, string name, BuiltInType dataType, int valueRank, byte accessLevel)
{
DataItemState variable = new DataItemState(parent);
variable.ValuePrecision = new PropertyState<double>(variable);
variable.Definition = new PropertyState<string>(variable);
variable.Create(
SystemContext,
null,
variable.BrowseName,
null,
true);
variable.SymbolicName = name;
variable.ReferenceTypeId = ReferenceTypes.Organizes;
variable.NodeId = new NodeId(id, NamespaceIndex);
variable.BrowseName = new QualifiedName(name, NamespaceIndex);
variable.DisplayName = new LocalizedText("en", name);
variable.WriteMask = AttributeWriteMask.None;
variable.UserWriteMask = AttributeWriteMask.None;
variable.DataType = (uint)dataType;
variable.ValueRank = valueRank;
variable.AccessLevel = accessLevel;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = Opc.Ua.TypeInfo.GetDefaultValue((uint)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 });
}
variable.ValuePrecision.Value = 2;
variable.ValuePrecision.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.ValuePrecision.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Definition.Value = String.Empty;
variable.Definition.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Definition.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
if (parent != null)
{
parent.AddChild(variable);
}
return variable;
}
/// <summary>
/// Creates a new variable.
/// </summary>
private BaseDataVariableState CreateVariable(NodeState parent, string path, string name, NodeId dataType, int valueRank)
{
BaseDataVariableState variable = new BaseDataVariableState(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypes.Organizes,
TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
NodeId = new NodeId(path, NamespaceIndex),
BrowseName = new QualifiedName(path, NamespaceIndex),
DisplayName = new LocalizedText("en", name),
WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description,
DataType = dataType,
ValueRank = valueRank,
AccessLevel = AccessLevels.CurrentReadOrWrite,
UserAccessLevel = AccessLevels.CurrentReadOrWrite,
Historizing = false,
StatusCode = StatusCodes.Good,
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;
}
/// <summary>
/// Creates a new method.
/// </summary>
private MethodState CreateMethod(NodeState parent, string path, string name)
{
MethodState method = new MethodState(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypeIds.HasComponent,
NodeId = new NodeId(path, NamespaceIndex),
BrowseName = new QualifiedName(path, NamespaceIndex),
DisplayName = new LocalizedText("en", name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None,
Executable = true,
UserExecutable = true
};
if (parent != null)
{
parent.AddChild(method);
}
return method;
}
/// <summary>
/// Creates a new method using type Numeric for the NodeId.
/// </summary>
private MethodState CreateMethod(NodeState parent, uint id, string name)
{
MethodState method = new MethodState(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypeIds.HasComponent,
NodeId = new NodeId(id, NamespaceIndex),
BrowseName = new QualifiedName(name, NamespaceIndex),
DisplayName = new LocalizedText("en", name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None,
Executable = true,
UserExecutable = true
};
if (parent != null)
{
parent.AddChild(method);
}
return method;
}
/// <summary>
/// Method to start monitoring a node and publish the data to IoTHub. Executes synchronously.
/// </summary>
private ServiceResult OnPublishNodeCall(ISystemContext context, MethodState method, IList<object> inputArguments, IList<object> outputArguments)
{
string logPrefix = "OnPublishNodeCall:";
if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
{
Logger.Error($"{logPrefix} Invalid Arguments when trying to publish a node.");
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments as strings!");
}
HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
NodeId nodeId = null;
ExpandedNodeId expandedNodeId = null;
Uri endpointUrl = null;
bool isNodeIdFormat = true;
try
{
string id = inputArguments[0] as string;
if (id.Contains("nsu="))
{
expandedNodeId = ExpandedNodeId.Parse(id);
isNodeIdFormat = false;
}
else
{
nodeId = NodeId.Parse(id);
isNodeIdFormat = true;
}
endpointUrl = new Uri(inputArguments[1] as string);
}
catch (UriFormatException)
{
Logger.Error($"{logPrefix} The EndpointUrl has an invalid format '{inputArguments[1] as string}'!");
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!");
}
catch (Exception e)
{
Logger.Error(e, $"{logPrefix} The NodeId has an invalid format '{inputArguments[0] as string}'!");
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA NodeId in NodeId or ExpandedNodeId format as first argument!");
}
// find/create a session to the endpoint URL and start monitoring the node.
try
{
// lock the publishing configuration till we are done
OpcSessionsListSemaphore.Wait();
if (ShutdownTokenSource.IsCancellationRequested)
{
return ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress.");
}
// find the session we need to monitor the node
OpcSession opcSession = null;
opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUrl.AbsoluteUri.Equals(endpointUrl.AbsoluteUri, StringComparison.OrdinalIgnoreCase));
// add a new session.
if (opcSession == null)
{
// create new session info.
opcSession = new OpcSession(endpointUrl, true, OpcSessionCreationTimeout);
OpcSessions.Add(opcSession);
Logger.Information($"OnPublishNodeCall: No matching session found for endpoint '{endpointUrl.OriginalString}'. Requested to create a new one.");
}
if (isNodeIdFormat)
{
// add the node info to the subscription with the default publishing interval, execute syncronously
Logger.Debug($"{logPrefix} Request to monitor item with NodeId '{nodeId.ToString()}' (with default PublishingInterval and SamplingInterval)");
statusCode = opcSession.AddNodeForMonitoringAsync(nodeId, null, null, null, null, ShutdownTokenSource.Token).Result;
}
else
{
// add the node info to the subscription with the default publishing interval, execute syncronously
Logger.Debug($"{logPrefix} Request to monitor item with ExpandedNodeId '{expandedNodeId.ToString()}' (with default PublishingInterval and SamplingInterval)");
statusCode = opcSession.AddNodeForMonitoringAsync(null, expandedNodeId, null, null, null, ShutdownTokenSource.Token).Result;
}
}
catch (Exception e)
{
Logger.Error(e, $"{logPrefix} Exception while trying to configure publishing node '{(isNodeIdFormat ? nodeId.ToString() : expandedNodeId.ToString())}'");
return ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error publishing node: {e.Message}");
}
finally
{
OpcSessionsListSemaphore.Release();
}
if (statusCode == HttpStatusCode.NotAcceptable)
{
return ServiceResult.Create(StatusCodes.BadSessionNotActivated, "Can not start monitoring node, because session is in connecting state. Please retry later!");
}
if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted)
{
return ServiceResult.Good;
}
return ServiceResult.Create(StatusCodes.Bad, "Can not start monitoring node! Reason unknown.");
}
/// <summary>
/// Method to remove the node from the subscription and stop publishing telemetry to IoTHub. Executes synchronously.
/// </summary>
private ServiceResult OnUnpublishNodeCall(ISystemContext context, MethodState method, IList<object> inputArguments, IList<object> outputArguments)
{
string logPrefix = "OnUnpublishNodeCall:";
if (string.IsNullOrEmpty(inputArguments[0] as string) || string.IsNullOrEmpty(inputArguments[1] as string))
{
Logger.Error($"{logPrefix} Invalid arguments!");
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide all arguments!");
}
HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
NodeId nodeId = null;
ExpandedNodeId expandedNodeId = null;
Uri endpointUrl = null;
bool isNodeIdFormat = true;
try
{
string id = inputArguments[0] as string;
if (id.Contains("nsu="))
{
expandedNodeId = ExpandedNodeId.Parse(id);
isNodeIdFormat = false;
}
else
{
nodeId = NodeId.Parse(id);
isNodeIdFormat = true;
}
endpointUrl = new Uri(inputArguments[1] as string);
}
catch (UriFormatException)
{
Logger.Error($"{logPrefix} The endpointUrl is invalid '{inputArguments[1] as string}'!");
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as second argument!");
}
catch (Exception e)
{
Logger.Error(e, $"{logPrefix} The NodeId has an invalid format '{inputArguments[0] as string}'!");
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA NodeId in NodeId or ExpandedNodeId format as first argument!");
}
// find the session and stop monitoring the node.
try
{
OpcSessionsListSemaphore.Wait();
if (ShutdownTokenSource.IsCancellationRequested)
{
return ServiceResult.Create(StatusCodes.BadUnexpectedError, $"Publisher shutdown in progress.");
}
// find the session we need to monitor the node
OpcSession opcSession = null;
try
{
opcSession = OpcSessions.FirstOrDefault(s => s.EndpointUrl.AbsoluteUri.Equals(endpointUrl.AbsoluteUri, StringComparison.OrdinalIgnoreCase));
}
catch
{
opcSession = null;
}
if (opcSession == null)
{
// do nothing if there is no session for this endpoint.
Logger.Error($"{logPrefix} Session for endpoint '{endpointUrl.OriginalString}' not found.");
return ServiceResult.Create(StatusCodes.BadSessionIdInvalid, "Session for endpoint of node to unpublished not found!");
}
else
{
if (isNodeIdFormat)
{
// stop monitoring the node, execute syncronously
Logger.Information($"{logPrefix} Request to stop monitoring item with NodeId '{nodeId.ToString()}')");
statusCode = opcSession.RequestMonitorItemRemovalAsync(nodeId, null, ShutdownTokenSource.Token).Result;
}
else
{
// stop monitoring the node, execute syncronously
Logger.Information($"{logPrefix} Request to stop monitoring item with ExpandedNodeId '{expandedNodeId.ToString()}')");
statusCode = opcSession.RequestMonitorItemRemovalAsync(null, expandedNodeId, ShutdownTokenSource.Token).Result;
}
}
}
catch (Exception e)
{
Logger.Error(e, $"{logPrefix} Exception while trying to configure publishing node '{nodeId.ToString()}'");
return ServiceResult.Create(e, StatusCodes.BadUnexpectedError, $"Unexpected error unpublishing node: {e.Message}");
}
finally
{
OpcSessionsListSemaphore.Release();
}
return (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted ? ServiceResult.Good : ServiceResult.Create(StatusCodes.Bad, "Can not stop monitoring node!"));
}
/// <summary>
/// Method to get the list of configured nodes and is only there for backward compatibility. Executes synchronously.
/// The format of the returned node description is using NodeId format. The assumption
/// is that the caller is able to access the namespace array of the server
/// on the endpoint URL(s) themselve and do the correct mapping.
/// </summary>
private ServiceResult OnGetPublishedNodesLegacyCall(ISystemContext context, MethodState method, IList<object> inputArguments, IList<object> outputArguments)
{
string logPrefix = "OnGetPublishedNodesLegacyCall:";
Uri endpointUrl = null;
if (string.IsNullOrEmpty(inputArguments[0] as string))
{
Logger.Information($"{logPrefix} returning all nodes of all endpoints'!");
}
else
{
try
{
endpointUrl = new Uri(inputArguments[0] as string);
}
catch (UriFormatException)
{
Logger.Error($"{logPrefix} The endpointUrl is invalid '{inputArguments[0] as string}'!");
return ServiceResult.Create(StatusCodes.BadArgumentsMissing, "Please provide a valid OPC UA endpoint URL as first argument!");
}
}
// get the list of published nodes in NodeId format
List<PublisherConfigurationFileEntryLegacyModel> configFileEntries = GetPublisherConfigurationFileEntriesAsNodeIdsAsync(endpointUrl).Result;
outputArguments[0] = JsonConvert.SerializeObject(configFileEntries);
Logger.Information($"{logPrefix} Success (number of entries: {configFileEntries.Count})");
return ServiceResult.Good;
}
}
}