mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 15:11:53 +08:00
1782 lines
78 KiB
C#
1782 lines
78 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Opc.Ua.PubSub.Configuration
|
|
{
|
|
/// <summary>
|
|
/// <para>Entity responsible to configure a PubSub Application</para>
|
|
/// <para>
|
|
/// It has methods for adding/removing configuration objects to a root <see cref="PubSubConfigurationDataType"/> object.
|
|
/// When the root <see cref="PubSubConfigurationDataType"/> object is modified there are various events raised to allow reaction to configuration changes.
|
|
/// Each child object from parent <see cref="PubSubConfigurationDataType"/> object has a configurationId associated to it and it can be used to alter configuration.
|
|
/// The configurationId can be obtained using the <see cref="FindIdForObject(object)"/> method.
|
|
/// </para>
|
|
/// </summary>
|
|
public class UaPubSubConfigurator
|
|
{
|
|
/// <summary>
|
|
/// Value of an uninitialized identifier.
|
|
/// </summary>
|
|
internal static uint InvalidId;
|
|
|
|
private readonly Lock m_lock = new();
|
|
private readonly ILogger m_logger;
|
|
private readonly ITelemetryContext m_telemetry;
|
|
private readonly Dictionary<uint, object> m_idsToObjects = [];
|
|
private readonly Dictionary<object, uint> m_objectsToIds = new(RefEqualityComparer.Default);
|
|
private readonly Dictionary<uint, PubSubState> m_idsToPubSubState = [];
|
|
private readonly Dictionary<uint, uint> m_idsToParentId = [];
|
|
private uint m_nextId = 1;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a published data set is added to the configurator
|
|
/// </summary>
|
|
public event EventHandler<PublishedDataSetEventArgs>? PublishedDataSetAdded;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a published data set is removed from the configurator
|
|
/// </summary>
|
|
public event EventHandler<PublishedDataSetEventArgs>? PublishedDataSetRemoved;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when an extension field is added to a published data set
|
|
/// </summary>
|
|
public event EventHandler<ExtensionFieldEventArgs>? ExtensionFieldAdded;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when an extension field is removed from a published data set
|
|
/// </summary>
|
|
public event EventHandler<ExtensionFieldEventArgs>? ExtensionFieldRemoved;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a connection is added to the configurator
|
|
/// </summary>
|
|
public event EventHandler<ConnectionEventArgs>? ConnectionAdded;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a connection is removed from the configurator
|
|
/// </summary>
|
|
public event EventHandler<ConnectionEventArgs>? ConnectionRemoved;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a WriterGroup is added to a connection
|
|
/// </summary>
|
|
public event EventHandler<WriterGroupEventArgs>? WriterGroupAdded;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a WriterGroup is removed from a connection
|
|
/// </summary>
|
|
public event EventHandler<WriterGroupEventArgs>? WriterGroupRemoved;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a ReaderGroup is added to a connection
|
|
/// </summary>
|
|
public event EventHandler<ReaderGroupEventArgs>? ReaderGroupAdded;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a ReaderGroup is removed from a connection
|
|
/// </summary>
|
|
public event EventHandler<ReaderGroupEventArgs>? ReaderGroupRemoved;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a DataSetWriter is added to a WriterGroup
|
|
/// </summary>
|
|
public event EventHandler<DataSetWriterEventArgs>? DataSetWriterAdded;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a DataSetWriter is removed from a WriterGroup
|
|
/// </summary>
|
|
public event EventHandler<DataSetWriterEventArgs>? DataSetWriterRemoved;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a DataSetreader is added to a ReaderGroup
|
|
/// </summary>
|
|
public event EventHandler<DataSetReaderEventArgs>? DataSetReaderAdded;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when a DataSetreader is removed from a ReaderGroup
|
|
/// </summary>
|
|
public event EventHandler<DataSetReaderEventArgs>? DataSetReaderRemoved;
|
|
|
|
/// <summary>
|
|
/// Event raised when the state of a configuration object is changed
|
|
/// </summary>
|
|
public event EventHandler<PubSubStateChangedEventArgs>? PubSubStateChanged;
|
|
|
|
/// <summary>
|
|
/// Create new instance of <see cref="UaPubSubConfigurator"/>.
|
|
/// </summary>
|
|
public UaPubSubConfigurator(ITelemetryContext telemetry)
|
|
{
|
|
m_logger = telemetry.CreateLogger<UaPubSubConfigurator>();
|
|
m_telemetry = telemetry;
|
|
|
|
PubSubConfiguration = new PubSubConfigurationDataType
|
|
{
|
|
Enabled = true,
|
|
Connections = [],
|
|
PublishedDataSets = []
|
|
};
|
|
|
|
//remember configuration id
|
|
uint id = m_nextId++;
|
|
m_objectsToIds.Add(PubSubConfiguration, id);
|
|
m_idsToObjects.Add(id, PubSubConfiguration);
|
|
m_idsToPubSubState.Add(id, GetInitialPubSubState(PubSubConfiguration));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get reference to <see cref="PubSubConfigurationDataType"/> instance that
|
|
/// maintains the configuration for this <see cref="UaPubSubConfigurator"/>.
|
|
/// </summary>
|
|
public PubSubConfigurationDataType PubSubConfiguration { get; }
|
|
|
|
/// <summary>
|
|
/// Search a configured <see cref="PublishedDataSetDataType"/> with the specified name and return it
|
|
/// </summary>
|
|
/// <param name="name">Name of the object to be found.
|
|
/// Returns null if name was not found.</param>
|
|
public PublishedDataSetDataType? FindPublishedDataSetByName(string name)
|
|
{
|
|
foreach (PublishedDataSetDataType publishedDataSet in PubSubConfiguration
|
|
.PublishedDataSets)
|
|
{
|
|
if (name == publishedDataSet.Name)
|
|
{
|
|
return publishedDataSet;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search objects in current configuration and return them
|
|
/// </summary>
|
|
/// <param name="id">Id of the object to be found.
|
|
/// Returns null if id was not found.</param>
|
|
public object? FindObjectById(uint id)
|
|
{
|
|
if (m_idsToObjects.TryGetValue(id, out object? objectById))
|
|
{
|
|
return objectById;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search id for specified configuration object.
|
|
/// </summary>
|
|
/// <param name="configurationObject">The object whose id is searched.</param>
|
|
/// <returns>Returns <see cref="InvalidId"/> if object was not found.</returns>
|
|
public uint FindIdForObject(object configurationObject)
|
|
{
|
|
if (m_objectsToIds.TryGetValue(configurationObject, out uint id))
|
|
{
|
|
return id;
|
|
}
|
|
return InvalidId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search <see cref="PubSubState"/> for specified configuration object.
|
|
/// </summary>
|
|
/// <param name="configurationObject">The object whose <see cref="PubSubState"/> is searched.</param>
|
|
/// <returns>Returns <see cref="PubSubState"/> if the object.</returns>
|
|
public PubSubState FindStateForObject(object configurationObject)
|
|
{
|
|
uint id = FindIdForObject(configurationObject);
|
|
if (m_idsToPubSubState.TryGetValue(id, out PubSubState pubSubState))
|
|
{
|
|
return pubSubState;
|
|
}
|
|
return PubSubState.Error;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search <see cref="PubSubState"/> for specified configuration object.
|
|
/// </summary>
|
|
/// <param name="id">The id of the object which <see cref="PubSubState"/> is searched.</param>
|
|
/// <returns>Returns <see cref="PubSubState"/> if the object.</returns>
|
|
public PubSubState FindStateForId(uint id)
|
|
{
|
|
if (m_idsToPubSubState.TryGetValue(id, out PubSubState pubsubState))
|
|
{
|
|
return pubsubState;
|
|
}
|
|
return PubSubState.Error;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the parent configuration object for a configuration object
|
|
/// </summary>
|
|
public object? FindParentForObject(object configurationObject)
|
|
{
|
|
uint id = FindIdForObject(configurationObject);
|
|
if (id != InvalidId && m_idsToParentId.TryGetValue(id, out uint parentId))
|
|
{
|
|
return FindObjectById(parentId);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find children ids for specified object
|
|
/// </summary>
|
|
public List<uint> FindChildrenIdsForObject(object configurationObject)
|
|
{
|
|
uint parentId = FindIdForObject(configurationObject);
|
|
|
|
var childrenIds = new List<uint>();
|
|
if (parentId != InvalidId && m_idsToParentId.ContainsValue(parentId))
|
|
{
|
|
foreach (uint key in m_idsToParentId.Keys)
|
|
{
|
|
if (m_idsToParentId[key] == parentId)
|
|
{
|
|
childrenIds.Add(key);
|
|
}
|
|
}
|
|
}
|
|
return childrenIds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load the specified configuration
|
|
/// </summary>
|
|
/// <param name="configFilePath">From where to load configuration</param>
|
|
/// <param name="replaceExisting"> flag that indicates if current configuration is overwritten</param>
|
|
/// <exception cref="ArgumentNullException"><paramref name="configFilePath"/> is <c>null</c>.</exception>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
public void LoadConfiguration(string configFilePath, bool replaceExisting = true)
|
|
{
|
|
// validate input argument
|
|
if (configFilePath == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(configFilePath));
|
|
}
|
|
if (!File.Exists(configFilePath))
|
|
{
|
|
throw new ArgumentException(
|
|
"The specified file {0} does not exist",
|
|
configFilePath);
|
|
}
|
|
PubSubConfigurationDataType pubSubConfiguration =
|
|
UaPubSubConfigurationHelper.LoadConfiguration(configFilePath, m_telemetry);
|
|
|
|
LoadConfiguration(pubSubConfiguration, replaceExisting);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load the specified configuration
|
|
/// </summary>
|
|
/// <param name="pubSubConfiguration">The configuration</param>
|
|
/// <param name="replaceExisting"> flag that indicates if current configuration is overwritten</param>
|
|
public void LoadConfiguration(
|
|
PubSubConfigurationDataType pubSubConfiguration,
|
|
bool replaceExisting = true)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (replaceExisting)
|
|
{
|
|
//remove previous configured published data sets
|
|
if (PubSubConfiguration.PublishedDataSets.Count > 0)
|
|
{
|
|
foreach (PublishedDataSetDataType publishedDataSet in pubSubConfiguration
|
|
.PublishedDataSets)
|
|
{
|
|
RemovePublishedDataSet(publishedDataSet);
|
|
}
|
|
}
|
|
|
|
//remove previous configured connections
|
|
if (PubSubConfiguration.Connections!.Count > 0)
|
|
{
|
|
// ToArray() of generated collection is annotated with possibly-null element flow.
|
|
foreach (PubSubConnectionDataType connection in PubSubConfiguration.Connections!.ToArray()!)
|
|
{
|
|
RemoveConnection(connection);
|
|
}
|
|
}
|
|
|
|
PubSubConfiguration.Connections = [];
|
|
PubSubConfiguration.PublishedDataSets = [];
|
|
}
|
|
|
|
//first load Published DataSet information
|
|
foreach (PublishedDataSetDataType publishedDataSet in pubSubConfiguration
|
|
.PublishedDataSets)
|
|
{
|
|
AddPublishedDataSet(publishedDataSet);
|
|
}
|
|
|
|
foreach (PubSubConnectionDataType pubSubConnectionDataType in pubSubConfiguration
|
|
.Connections)
|
|
{
|
|
// handle empty names
|
|
if (string.IsNullOrEmpty(pubSubConnectionDataType.Name))
|
|
{
|
|
//set default name
|
|
pubSubConnectionDataType.Name = "Connection_" + (m_nextId + 1);
|
|
}
|
|
AddConnection(pubSubConnectionDataType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a published data set to current configuration.
|
|
/// </summary>
|
|
/// <param name="publishedDataSetDataType">The <see cref="PublishedDataSetDataType"/> object to be added to configuration.</param>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
public StatusCode AddPublishedDataSet(PublishedDataSetDataType publishedDataSetDataType)
|
|
{
|
|
if (m_objectsToIds.ContainsKey(publishedDataSetDataType))
|
|
{
|
|
throw new ArgumentException(
|
|
"This PublishedDataSetDataType instance is already added to the configuration.");
|
|
}
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
//validate duplicate name
|
|
bool duplicateName = false;
|
|
foreach (PublishedDataSetDataType publishedDataSet in PubSubConfiguration
|
|
.PublishedDataSets)
|
|
{
|
|
if (publishedDataSetDataType.Name == publishedDataSet.Name)
|
|
{
|
|
duplicateName = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicateName)
|
|
{
|
|
m_logger.LogError(
|
|
"Attempted to add PublishedDataSetDataType with duplicate name = {Name}",
|
|
publishedDataSetDataType.Name);
|
|
return StatusCodes.BadBrowseNameDuplicated;
|
|
}
|
|
|
|
uint newPublishedDataSetId = m_nextId++;
|
|
//remember connection
|
|
m_idsToObjects.Add(newPublishedDataSetId, publishedDataSetDataType);
|
|
m_objectsToIds.Add(publishedDataSetDataType, newPublishedDataSetId);
|
|
PubSubConfiguration.PublishedDataSets += publishedDataSetDataType;
|
|
|
|
// raise PublishedDataSetAdded event
|
|
PublishedDataSetAdded?.Invoke(
|
|
this,
|
|
new PublishedDataSetEventArgs
|
|
{
|
|
PublishedDataSetId = newPublishedDataSetId,
|
|
PublishedDataSetDataType = publishedDataSetDataType
|
|
});
|
|
|
|
ArrayOf<KeyValuePair> extensionFields = publishedDataSetDataType.ExtensionFields;
|
|
publishedDataSetDataType.ExtensionFields = [];
|
|
foreach (KeyValuePair extensionField in extensionFields)
|
|
{
|
|
AddExtensionField(newPublishedDataSetId, extensionField);
|
|
}
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.AddPublishedDataSet: Exception");
|
|
}
|
|
|
|
//todo implement state validation
|
|
return StatusCodes.Bad;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a published data set from current configuration.
|
|
/// </summary>
|
|
/// <param name="publishedDataSetId">Id of the published data set to be removed.</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> if operation is successful,
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> otherwise.
|
|
/// </returns>
|
|
public StatusCode RemovePublishedDataSet(uint publishedDataSetId)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (FindObjectById(
|
|
publishedDataSetId) is not PublishedDataSetDataType publishedDataSetDataType)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogInformation(
|
|
"Current configuration does not contain PublishedDataSetDataType with ConfigId = {PublishedDataSetId}",
|
|
publishedDataSetId);
|
|
return StatusCodes.Good;
|
|
}
|
|
return RemovePublishedDataSet(publishedDataSetDataType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a published data set from current configuration.
|
|
/// </summary>
|
|
/// <param name="publishedDataSetDataType">The published data set to be removed.</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> if operation is successful,
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> otherwise.
|
|
/// </returns>
|
|
public StatusCode RemovePublishedDataSet(PublishedDataSetDataType publishedDataSetDataType)
|
|
{
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
uint publishedDataSetId = FindIdForObject(publishedDataSetDataType);
|
|
if (publishedDataSetDataType != null && publishedDataSetId != InvalidId)
|
|
{
|
|
/*A successful removal of the PublishedDataSetType Object removes all associated DataSetWriter Objects.
|
|
* Before the Objects are removed, their state is changed to Disabled_0*/
|
|
|
|
// Find all associated DataSetWriter objects
|
|
foreach (PubSubConnectionDataType connection in PubSubConfiguration
|
|
.Connections)
|
|
{
|
|
foreach (WriterGroupDataType writerGroup in connection.WriterGroups!)
|
|
{
|
|
foreach (DataSetWriterDataType dataSetWriter in writerGroup.DataSetWriters!.ToArray()!)
|
|
{
|
|
if (dataSetWriter.DataSetName == publishedDataSetDataType.Name)
|
|
{
|
|
RemoveDataSetWriter(dataSetWriter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PubSubConfiguration.PublishedDataSets =
|
|
PubSubConfiguration.PublishedDataSets.RemoveItem(publishedDataSetDataType);
|
|
|
|
//remove all references from dictionaries
|
|
m_idsToObjects.Remove(publishedDataSetId);
|
|
m_objectsToIds.Remove(publishedDataSetDataType);
|
|
m_idsToParentId.Remove(publishedDataSetId);
|
|
m_idsToPubSubState.Remove(publishedDataSetId);
|
|
|
|
PublishedDataSetRemoved?.Invoke(
|
|
this,
|
|
new PublishedDataSetEventArgs
|
|
{
|
|
PublishedDataSetId = publishedDataSetId,
|
|
PublishedDataSetDataType = publishedDataSetDataType
|
|
});
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.RemovePublishedDataSet: Exception");
|
|
}
|
|
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add Extension field to the specified publishedDataSet
|
|
/// </summary>
|
|
public StatusCode AddExtensionField(
|
|
uint publishedDataSetConfigId,
|
|
KeyValuePair extensionField)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (FindObjectById(
|
|
publishedDataSetConfigId) is not PublishedDataSetDataType publishedDataSetDataType)
|
|
{
|
|
return StatusCodes.BadNodeIdInvalid;
|
|
}
|
|
if (!publishedDataSetDataType.ExtensionFields.IsEmpty)
|
|
{
|
|
//validate duplicate name
|
|
bool duplicateName = false;
|
|
foreach (KeyValuePair element in publishedDataSetDataType.ExtensionFields)
|
|
{
|
|
if (element.Key == extensionField.Key)
|
|
{
|
|
duplicateName = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicateName)
|
|
{
|
|
m_logger.LogError(
|
|
"AddExtensionField - A field with the name already exists. Duplicate name = {Name}",
|
|
extensionField.Key);
|
|
return StatusCodes.BadNodeIdExists;
|
|
}
|
|
}
|
|
uint newextensionFieldId = m_nextId++;
|
|
//remember connection
|
|
m_idsToObjects.Add(newextensionFieldId, extensionField);
|
|
m_objectsToIds.Add(extensionField, newextensionFieldId);
|
|
publishedDataSetDataType.ExtensionFields += extensionField;
|
|
|
|
// raise ExtensionFieldAdded event
|
|
ExtensionFieldAdded?.Invoke(
|
|
this,
|
|
new ExtensionFieldEventArgs
|
|
{
|
|
PublishedDataSetId = publishedDataSetConfigId,
|
|
ExtensionFieldId = newextensionFieldId,
|
|
ExtensionField = extensionField
|
|
});
|
|
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an extension field from a published data set
|
|
/// </summary>
|
|
public StatusCode RemoveExtensionField(
|
|
uint publishedDataSetConfigId,
|
|
uint extensionFieldConfigId)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if ((FindObjectById(
|
|
publishedDataSetConfigId) is not PublishedDataSetDataType publishedDataSetDataType) ||
|
|
(FindObjectById(
|
|
extensionFieldConfigId) is not KeyValuePair extensionFieldToRemove))
|
|
{
|
|
return StatusCodes.BadNodeIdInvalid;
|
|
}
|
|
if (publishedDataSetDataType.ExtensionFields.IsEmpty)
|
|
{
|
|
return StatusCodes.BadNodeIdInvalid;
|
|
}
|
|
// locate the extension field
|
|
foreach (KeyValuePair extensionField in publishedDataSetDataType.ExtensionFields!.ToArray()!)
|
|
{
|
|
if (extensionField.Equals(extensionFieldToRemove))
|
|
{
|
|
publishedDataSetDataType.ExtensionFields =
|
|
publishedDataSetDataType.ExtensionFields.RemoveItem(extensionFieldToRemove);
|
|
|
|
// raise ExtensionFieldRemoved event
|
|
ExtensionFieldRemoved?.Invoke(
|
|
this,
|
|
new ExtensionFieldEventArgs
|
|
{
|
|
PublishedDataSetId = publishedDataSetConfigId,
|
|
ExtensionFieldId = extensionFieldConfigId,
|
|
ExtensionField = extensionField
|
|
});
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
}
|
|
return StatusCodes.BadNodeIdInvalid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a connection to current configuration.
|
|
/// </summary>
|
|
/// <param name="pubSubConnectionDataType">The <see cref="PubSubConnectionDataType"/> object that configures the new connection.</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The connection was added with success.
|
|
/// - <see cref="StatusCodes.BadBrowseNameDuplicated"/> An Object with the name already exists.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error adding the connection.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
public StatusCode AddConnection(PubSubConnectionDataType pubSubConnectionDataType)
|
|
{
|
|
if (m_objectsToIds.ContainsKey(pubSubConnectionDataType))
|
|
{
|
|
throw new ArgumentException(
|
|
"This PubSubConnectionDataType instance is already added to the configuration.");
|
|
}
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
//validate connection name
|
|
bool duplicateName = false;
|
|
foreach (PubSubConnectionDataType connection in PubSubConfiguration.Connections)
|
|
{
|
|
if (connection.Name == pubSubConnectionDataType.Name)
|
|
{
|
|
duplicateName = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicateName)
|
|
{
|
|
m_logger.LogError(
|
|
"Attempted to add PubSubConnectionDataType with duplicate name = {Name}",
|
|
pubSubConnectionDataType.Name);
|
|
return StatusCodes.BadBrowseNameDuplicated;
|
|
}
|
|
|
|
// remember collections
|
|
ArrayOf<WriterGroupDataType> writerGroups = pubSubConnectionDataType.WriterGroups;
|
|
pubSubConnectionDataType.WriterGroups = [];
|
|
ArrayOf<ReaderGroupDataType> readerGroups = pubSubConnectionDataType.ReaderGroups;
|
|
pubSubConnectionDataType.ReaderGroups = [];
|
|
|
|
uint newConnectionId = m_nextId++;
|
|
//remember connection
|
|
m_idsToObjects.Add(newConnectionId, pubSubConnectionDataType);
|
|
m_objectsToIds.Add(pubSubConnectionDataType, newConnectionId);
|
|
// remember parent id
|
|
m_idsToParentId.Add(newConnectionId, FindIdForObject(PubSubConfiguration));
|
|
//remember initial state
|
|
m_idsToPubSubState.Add(
|
|
newConnectionId,
|
|
GetInitialPubSubState(pubSubConnectionDataType));
|
|
|
|
PubSubConfiguration.Connections += pubSubConnectionDataType;
|
|
|
|
// raise ConnectionAdded event
|
|
ConnectionAdded?.Invoke(
|
|
this,
|
|
new ConnectionEventArgs
|
|
{
|
|
ConnectionId = newConnectionId,
|
|
PubSubConnectionDataType = pubSubConnectionDataType
|
|
});
|
|
//handler reader & writer groups
|
|
foreach (WriterGroupDataType writerGroup in writerGroups)
|
|
{
|
|
// handle empty names
|
|
if (string.IsNullOrEmpty(writerGroup.Name))
|
|
{
|
|
//set default name
|
|
writerGroup.Name = "WriterGroup_" + (m_nextId + 1);
|
|
}
|
|
AddWriterGroup(newConnectionId, writerGroup);
|
|
}
|
|
foreach (ReaderGroupDataType readerGroup in readerGroups)
|
|
{
|
|
// handle empty names
|
|
if (string.IsNullOrEmpty(readerGroup.Name))
|
|
{
|
|
//set default name
|
|
readerGroup.Name = "ReaderGroup_" + (m_nextId + 1);
|
|
}
|
|
AddReaderGroup(newConnectionId, readerGroup);
|
|
}
|
|
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.AddConnection: Exception");
|
|
}
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a connection from current configuration.
|
|
/// </summary>
|
|
/// <param name="connectionId">Id of the connection to be removed.</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The Connection was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the Connection.
|
|
/// </returns>
|
|
public StatusCode RemoveConnection(uint connectionId)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (FindObjectById(
|
|
connectionId) is not PubSubConnectionDataType pubSubConnectionDataType)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogInformation(
|
|
"Current configuration does not contain PubSubConnectionDataType with ConfigId = {ConnectionId}",
|
|
connectionId);
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
return RemoveConnection(pubSubConnectionDataType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a connection from current configuration.
|
|
/// </summary>
|
|
/// <param name="pubSubConnectionDataType">The connection to be removed.</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The Connection was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the Connection.
|
|
/// </returns>
|
|
public StatusCode RemoveConnection(PubSubConnectionDataType pubSubConnectionDataType)
|
|
{
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
uint connectionId = FindIdForObject(pubSubConnectionDataType);
|
|
if (pubSubConnectionDataType != null && connectionId != InvalidId)
|
|
{
|
|
// remove children
|
|
foreach (
|
|
WriterGroupDataType writerGroup in pubSubConnectionDataType.WriterGroups)
|
|
{
|
|
RemoveWriterGroup(writerGroup);
|
|
}
|
|
foreach (
|
|
ReaderGroupDataType readerGroup in pubSubConnectionDataType.ReaderGroups)
|
|
{
|
|
RemoveReaderGroup(readerGroup);
|
|
}
|
|
PubSubConfiguration.Connections =
|
|
PubSubConfiguration.Connections.RemoveItem(pubSubConnectionDataType);
|
|
|
|
//remove all references from dictionaries
|
|
m_idsToObjects.Remove(connectionId);
|
|
m_objectsToIds.Remove(pubSubConnectionDataType);
|
|
m_idsToParentId.Remove(connectionId);
|
|
m_idsToPubSubState.Remove(connectionId);
|
|
|
|
ConnectionRemoved?.Invoke(
|
|
this,
|
|
new ConnectionEventArgs
|
|
{
|
|
ConnectionId = connectionId,
|
|
PubSubConnectionDataType = pubSubConnectionDataType
|
|
});
|
|
return StatusCodes.Good;
|
|
}
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.RemoveConnection: Exception");
|
|
}
|
|
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a writerGroup to the specified connection
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The WriterGroup was added with success.
|
|
/// - <see cref="StatusCodes.BadBrowseNameDuplicated"/> An Object with the name already exists.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error adding the WriterGroup.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
public StatusCode AddWriterGroup(
|
|
uint parentConnectionId,
|
|
WriterGroupDataType writerGroupDataType)
|
|
{
|
|
if (m_objectsToIds.ContainsKey(writerGroupDataType))
|
|
{
|
|
throw new ArgumentException(
|
|
"This WriterGroupDataType instance is already added to the configuration.");
|
|
}
|
|
if (!m_idsToObjects.TryGetValue(parentConnectionId, out object? value))
|
|
{
|
|
throw new ArgumentException(
|
|
Utils.Format(
|
|
"There is no connection with configurationId = {0} in current configuration.",
|
|
parentConnectionId));
|
|
}
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
// remember collections
|
|
ArrayOf<DataSetWriterDataType> dataSetWriters = writerGroupDataType.DataSetWriters;
|
|
writerGroupDataType.DataSetWriters = [];
|
|
if (value is PubSubConnectionDataType parentConnection)
|
|
{
|
|
//validate duplicate name
|
|
bool duplicateName = false;
|
|
foreach (WriterGroupDataType writerGroup in parentConnection.WriterGroups)
|
|
{
|
|
if (writerGroup.Name == writerGroupDataType.Name)
|
|
{
|
|
duplicateName = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicateName)
|
|
{
|
|
m_logger.LogError(
|
|
"Attempted to add WriterGroupDataType with duplicate name = {Name}",
|
|
writerGroupDataType.Name);
|
|
return StatusCodes.BadBrowseNameDuplicated;
|
|
}
|
|
|
|
uint newWriterGroupId = m_nextId++;
|
|
//remember writer group
|
|
m_idsToObjects.Add(newWriterGroupId, writerGroupDataType);
|
|
m_objectsToIds.Add(writerGroupDataType, newWriterGroupId);
|
|
parentConnection.WriterGroups += writerGroupDataType;
|
|
|
|
// remember parent id
|
|
m_idsToParentId.Add(newWriterGroupId, parentConnectionId);
|
|
//remember initial state
|
|
m_idsToPubSubState.Add(
|
|
newWriterGroupId,
|
|
GetInitialPubSubState(writerGroupDataType));
|
|
|
|
// raise WriterGroupAdded event
|
|
WriterGroupAdded?.Invoke(
|
|
this,
|
|
new WriterGroupEventArgs
|
|
{
|
|
ConnectionId = parentConnectionId,
|
|
WriterGroupId = newWriterGroupId,
|
|
WriterGroupDataType = writerGroupDataType
|
|
});
|
|
|
|
//handler datasetWriters
|
|
foreach (DataSetWriterDataType datasetWriter in dataSetWriters)
|
|
{
|
|
// handle empty names
|
|
if (string.IsNullOrEmpty(datasetWriter.Name))
|
|
{
|
|
//set default name
|
|
datasetWriter.Name = "DataSetWriter_" + (m_nextId + 1);
|
|
}
|
|
AddDataSetWriter(newWriterGroupId, datasetWriter);
|
|
}
|
|
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.AddWriterGroup: Exception");
|
|
}
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a WriterGroupDataType instance from current configuration specified by configId
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The WriterGroup was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the WriterGroup.
|
|
/// </returns>
|
|
public StatusCode RemoveWriterGroup(uint writerGroupId)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (FindObjectById(writerGroupId) is not WriterGroupDataType writerGroupDataType)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogInformation(
|
|
"Current configuration does not contain WriterGroupDataType with ConfigId = {WriterGroupId}",
|
|
writerGroupId);
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
return RemoveWriterGroup(writerGroupDataType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a WriterGroupDataType instance from current configuration
|
|
/// </summary>
|
|
/// <param name="writerGroupDataType">Instance to remove</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The WriterGroup was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the WriterGroup.
|
|
/// </returns>
|
|
public StatusCode RemoveWriterGroup(WriterGroupDataType writerGroupDataType)
|
|
{
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
uint writerGroupId = FindIdForObject(writerGroupDataType);
|
|
if (writerGroupDataType != null && writerGroupId != InvalidId)
|
|
{
|
|
// remove children
|
|
foreach (DataSetWriterDataType dataSetWriter in writerGroupDataType.DataSetWriters)
|
|
{
|
|
RemoveDataSetWriter(dataSetWriter);
|
|
}
|
|
// find parent connection
|
|
var parentConnection = FindParentForObject(
|
|
writerGroupDataType) as PubSubConnectionDataType;
|
|
// TODO: FindIdForObject throws if parentConnection is null; null check below is unreachable.
|
|
uint parentConnectionId = FindIdForObject(parentConnection!);
|
|
if (parentConnection != null && parentConnectionId != InvalidId)
|
|
{
|
|
parentConnection.WriterGroups =
|
|
parentConnection.WriterGroups.RemoveItem(writerGroupDataType);
|
|
|
|
//remove all references from dictionaries
|
|
m_idsToObjects.Remove(writerGroupId);
|
|
m_objectsToIds.Remove(writerGroupDataType);
|
|
m_idsToParentId.Remove(writerGroupId);
|
|
m_idsToPubSubState.Remove(writerGroupId);
|
|
|
|
WriterGroupRemoved?.Invoke(
|
|
this,
|
|
new WriterGroupEventArgs
|
|
{
|
|
WriterGroupId = writerGroupId,
|
|
WriterGroupDataType = writerGroupDataType,
|
|
ConnectionId = parentConnectionId
|
|
});
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.RemoveWriterGroup: Exception");
|
|
}
|
|
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a DataSetWriter to the specified writer group
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The DataSetWriter was added with success.
|
|
/// - <see cref="StatusCodes.BadBrowseNameDuplicated"/> An Object with the name already exists.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error adding the DataSetWriter.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
public StatusCode AddDataSetWriter(
|
|
uint parentWriterGroupId,
|
|
DataSetWriterDataType dataSetWriterDataType)
|
|
{
|
|
if (m_objectsToIds.ContainsKey(dataSetWriterDataType))
|
|
{
|
|
throw new ArgumentException(
|
|
"This DataSetWriterDataType instance is already added to the configuration.");
|
|
}
|
|
if (!m_idsToObjects.TryGetValue(parentWriterGroupId, out object? value))
|
|
{
|
|
throw new ArgumentException(
|
|
Utils.Format(
|
|
"There is no WriterGroup with configurationId = {0} in current configuration.",
|
|
parentWriterGroupId));
|
|
}
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (value is WriterGroupDataType parentWriterGroup)
|
|
{
|
|
//validate duplicate name
|
|
bool duplicateName = false;
|
|
foreach (DataSetWriterDataType writer in parentWriterGroup.DataSetWriters)
|
|
{
|
|
if (writer.Name == dataSetWriterDataType.Name)
|
|
{
|
|
duplicateName = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicateName)
|
|
{
|
|
m_logger.LogError(
|
|
"Attempted to add DataSetWriterDataType with duplicate name = {Name}",
|
|
dataSetWriterDataType.Name);
|
|
return StatusCodes.BadBrowseNameDuplicated;
|
|
}
|
|
|
|
uint newDataSetWriterId = m_nextId++;
|
|
//remember connection
|
|
m_idsToObjects.Add(newDataSetWriterId, dataSetWriterDataType);
|
|
m_objectsToIds.Add(dataSetWriterDataType, newDataSetWriterId);
|
|
parentWriterGroup.DataSetWriters += dataSetWriterDataType;
|
|
|
|
// remember parent id
|
|
m_idsToParentId.Add(newDataSetWriterId, parentWriterGroupId);
|
|
|
|
//remember initial state
|
|
m_idsToPubSubState.Add(
|
|
newDataSetWriterId,
|
|
GetInitialPubSubState(dataSetWriterDataType));
|
|
|
|
// raise DataSetWriterAdded event
|
|
DataSetWriterAdded?.Invoke(
|
|
this,
|
|
new DataSetWriterEventArgs
|
|
{
|
|
WriterGroupId = parentWriterGroupId,
|
|
DataSetWriterId = newDataSetWriterId,
|
|
DataSetWriterDataType = dataSetWriterDataType
|
|
});
|
|
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.AddDataSetWriter: Exception");
|
|
}
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a DataSetWriterDataType instance from current configuration specified by configId
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The DataSetWriter was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the DataSetWriter.
|
|
/// </returns>
|
|
public StatusCode RemoveDataSetWriter(uint dataSetWriterId)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (FindObjectById(
|
|
dataSetWriterId) is not DataSetWriterDataType dataSetWriterDataType)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogInformation(
|
|
"Current configuration does not contain DataSetWriterDataType with ConfigId = {DataSetWriterId}",
|
|
dataSetWriterId);
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
return RemoveDataSetWriter(dataSetWriterDataType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a DataSetWriterDataType instance from current configuration
|
|
/// </summary>
|
|
/// <param name="dataSetWriterDataType">Instance to remove</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The DataSetWriter was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the DataSetWriter.
|
|
/// </returns>
|
|
public StatusCode RemoveDataSetWriter(DataSetWriterDataType dataSetWriterDataType)
|
|
{
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
uint dataSetWriterId = FindIdForObject(dataSetWriterDataType);
|
|
if (dataSetWriterDataType != null && dataSetWriterId != InvalidId)
|
|
{
|
|
// find parent writerGroup
|
|
var parentWriterGroup = FindParentForObject(dataSetWriterDataType) as
|
|
WriterGroupDataType;
|
|
// TODO: FindIdForObject throws if parentWriterGroup is null; null check below is unreachable.
|
|
uint parentWriterGroupId = FindIdForObject(parentWriterGroup!);
|
|
if (parentWriterGroup != null && parentWriterGroupId != InvalidId)
|
|
{
|
|
parentWriterGroup.DataSetWriters =
|
|
parentWriterGroup.DataSetWriters.RemoveItem(dataSetWriterDataType);
|
|
|
|
//remove all references from dictionaries
|
|
m_idsToObjects.Remove(dataSetWriterId);
|
|
m_objectsToIds.Remove(dataSetWriterDataType);
|
|
m_idsToParentId.Remove(dataSetWriterId);
|
|
m_idsToPubSubState.Remove(dataSetWriterId);
|
|
|
|
DataSetWriterRemoved?.Invoke(
|
|
this,
|
|
new DataSetWriterEventArgs
|
|
{
|
|
WriterGroupId = parentWriterGroupId,
|
|
DataSetWriterDataType = dataSetWriterDataType,
|
|
DataSetWriterId = dataSetWriterId
|
|
});
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.RemoveDataSetWriter: Exception");
|
|
}
|
|
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a readerGroup to the specified connection
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The ReaderGroup was added with success.
|
|
/// - <see cref="StatusCodes.BadBrowseNameDuplicated"/> An Object with the name already exists.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error adding the ReaderGroup.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
public StatusCode AddReaderGroup(
|
|
uint parentConnectionId,
|
|
ReaderGroupDataType readerGroupDataType)
|
|
{
|
|
if (m_objectsToIds.ContainsKey(readerGroupDataType))
|
|
{
|
|
throw new ArgumentException(
|
|
"This ReaderGroupDataType instance is already added to the configuration.");
|
|
}
|
|
if (!m_idsToObjects.TryGetValue(parentConnectionId, out object? value))
|
|
{
|
|
throw new ArgumentException(
|
|
Utils.Format(
|
|
"There is no connection with configurationId = {0} in current configuration.",
|
|
parentConnectionId));
|
|
}
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
// remember collections
|
|
ArrayOf<DataSetReaderDataType> dataSetReaders = readerGroupDataType.DataSetReaders;
|
|
readerGroupDataType.DataSetReaders = [];
|
|
if (value is PubSubConnectionDataType parentConnection)
|
|
{
|
|
//validate duplicate name
|
|
bool duplicateName = false;
|
|
foreach (ReaderGroupDataType readerGroup in parentConnection.ReaderGroups)
|
|
{
|
|
if (readerGroup.Name == readerGroupDataType.Name)
|
|
{
|
|
duplicateName = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicateName)
|
|
{
|
|
m_logger.LogError(
|
|
"Attempted to add ReaderGroupDataType with duplicate name = {Name}",
|
|
readerGroupDataType.Name);
|
|
return StatusCodes.BadBrowseNameDuplicated;
|
|
}
|
|
|
|
uint newReaderGroupId = m_nextId++;
|
|
//remember reader group
|
|
m_idsToObjects.Add(newReaderGroupId, readerGroupDataType);
|
|
m_objectsToIds.Add(readerGroupDataType, newReaderGroupId);
|
|
parentConnection.ReaderGroups += readerGroupDataType;
|
|
|
|
// remember parent id
|
|
m_idsToParentId.Add(newReaderGroupId, parentConnectionId);
|
|
|
|
//remember initial state
|
|
m_idsToPubSubState.Add(
|
|
newReaderGroupId,
|
|
GetInitialPubSubState(readerGroupDataType));
|
|
|
|
// raise ReaderGroupAdded event
|
|
ReaderGroupAdded?.Invoke(
|
|
this,
|
|
new ReaderGroupEventArgs
|
|
{
|
|
ConnectionId = parentConnectionId,
|
|
ReaderGroupId = newReaderGroupId,
|
|
ReaderGroupDataType = readerGroupDataType
|
|
});
|
|
|
|
//handler datasetWriters
|
|
foreach (DataSetReaderDataType datasetReader in dataSetReaders)
|
|
{
|
|
// handle empty names
|
|
if (string.IsNullOrEmpty(datasetReader.Name))
|
|
{
|
|
//set default name
|
|
datasetReader.Name = "DataSetReader_" + (m_nextId + 1);
|
|
}
|
|
AddDataSetReader(newReaderGroupId, datasetReader);
|
|
}
|
|
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.AddReaderGroup: Exception");
|
|
}
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a ReaderGroupDataType instance from current configuration specified by configId
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The ReaderGroup was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the ReaderGroup.
|
|
/// </returns>
|
|
public StatusCode RemoveReaderGroup(uint readerGroupId)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (FindObjectById(readerGroupId) is not ReaderGroupDataType readerGroupDataType)
|
|
{
|
|
m_logger.LogInformation(
|
|
"Current configuration does not contain ReaderGroupDataType with ConfigId = {ReaderGroupId}",
|
|
readerGroupId);
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
return RemoveReaderGroup(readerGroupDataType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a ReaderGroupDataType instance from current configuration
|
|
/// </summary>
|
|
/// <param name="readerGroupDataType">Instance to remove</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The ReaderGroup was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the ReaderGroup.
|
|
/// </returns>
|
|
public StatusCode RemoveReaderGroup(ReaderGroupDataType readerGroupDataType)
|
|
{
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
uint readerGroupId = FindIdForObject(readerGroupDataType);
|
|
if (readerGroupDataType != null && readerGroupId != InvalidId)
|
|
{
|
|
// remove children
|
|
foreach (DataSetReaderDataType dataSetReader in readerGroupDataType.DataSetReaders)
|
|
{
|
|
RemoveDataSetReader(dataSetReader);
|
|
}
|
|
// find parent connection
|
|
var parentConnection = FindParentForObject(
|
|
readerGroupDataType) as PubSubConnectionDataType;
|
|
// TODO: FindIdForObject throws if parentConnection is null; null check below is unreachable.
|
|
uint parentConnectionId = FindIdForObject(parentConnection!);
|
|
if (parentConnection != null && parentConnectionId != InvalidId)
|
|
{
|
|
parentConnection.ReaderGroups =
|
|
parentConnection.ReaderGroups.RemoveItem(readerGroupDataType);
|
|
|
|
//remove all references from dictionaries
|
|
m_idsToObjects.Remove(readerGroupId);
|
|
m_objectsToIds.Remove(readerGroupDataType);
|
|
m_idsToParentId.Remove(readerGroupId);
|
|
m_idsToPubSubState.Remove(readerGroupId);
|
|
|
|
ReaderGroupRemoved?.Invoke(
|
|
this,
|
|
new ReaderGroupEventArgs
|
|
{
|
|
ReaderGroupId = readerGroupId,
|
|
ReaderGroupDataType = readerGroupDataType,
|
|
ConnectionId = parentConnectionId
|
|
});
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.RemoveReaderGroup: Exception");
|
|
}
|
|
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a DataSetReader to the specified reader group
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The DataSetReader was added with success.
|
|
/// - <see cref="StatusCodes.BadBrowseNameDuplicated"/> An Object with the name already exists.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error adding the DataSetReader.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentException"></exception>
|
|
public StatusCode AddDataSetReader(
|
|
uint parentReaderGroupId,
|
|
DataSetReaderDataType dataSetReaderDataType)
|
|
{
|
|
if (m_objectsToIds.ContainsKey(dataSetReaderDataType))
|
|
{
|
|
throw new ArgumentException(
|
|
"This DataSetReaderDataType instance is already added to the configuration.");
|
|
}
|
|
if (!m_idsToObjects.TryGetValue(parentReaderGroupId, out object? value))
|
|
{
|
|
throw new ArgumentException(
|
|
Utils.Format(
|
|
"There is no ReaderGroup with configurationId = {0} in current configuration.",
|
|
parentReaderGroupId));
|
|
}
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (value is ReaderGroupDataType parentReaderGroup)
|
|
{
|
|
//validate duplicate name
|
|
bool duplicateName = false;
|
|
foreach (DataSetReaderDataType reader in parentReaderGroup.DataSetReaders)
|
|
{
|
|
if (reader.Name == dataSetReaderDataType.Name)
|
|
{
|
|
duplicateName = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicateName)
|
|
{
|
|
m_logger.LogError(
|
|
"Attempted to add DataSetReaderDataType with duplicate name = {Name}",
|
|
dataSetReaderDataType.Name);
|
|
return StatusCodes.BadBrowseNameDuplicated;
|
|
}
|
|
|
|
uint newDataSetReaderId = m_nextId++;
|
|
//remember connection
|
|
m_idsToObjects.Add(newDataSetReaderId, dataSetReaderDataType);
|
|
m_objectsToIds.Add(dataSetReaderDataType, newDataSetReaderId);
|
|
parentReaderGroup.DataSetReaders += dataSetReaderDataType;
|
|
|
|
// remember parent id
|
|
m_idsToParentId.Add(newDataSetReaderId, parentReaderGroupId);
|
|
|
|
//remember initial state
|
|
m_idsToPubSubState.Add(
|
|
newDataSetReaderId,
|
|
GetInitialPubSubState(dataSetReaderDataType));
|
|
|
|
// raise WriterGroupAdded event
|
|
DataSetReaderAdded?.Invoke(
|
|
this,
|
|
new DataSetReaderEventArgs
|
|
{
|
|
ReaderGroupId = parentReaderGroupId,
|
|
DataSetReaderId = newDataSetReaderId,
|
|
DataSetReaderDataType = dataSetReaderDataType
|
|
});
|
|
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.AddDataSetReader: Exception");
|
|
}
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a DataSetReaderDataType instance from current configuration specified by configId
|
|
/// </summary>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The DataSetWriter was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the DataSetWriter.
|
|
/// </returns>
|
|
public StatusCode RemoveDataSetReader(uint dataSetReaderId)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (FindObjectById(
|
|
dataSetReaderId) is not DataSetReaderDataType dataSetReaderDataType)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogInformation(
|
|
"Current configuration does not contain DataSetReaderDataType with ConfigId = {DataSetReaderId}",
|
|
dataSetReaderId);
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
return RemoveDataSetReader(dataSetReaderDataType);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a DataSetReaderDataType instance from current configuration
|
|
/// </summary>
|
|
/// <param name="dataSetReaderDataType">Instance to remove</param>
|
|
/// <returns>
|
|
/// - <see cref="StatusCodes.Good"/> The DataSetWriter was removed with success.
|
|
/// - <see cref="StatusCodes.BadNodeIdUnknown"/> The GroupId is unknown.
|
|
/// - <see cref="StatusCodes.BadInvalidArgument"/> There was an error removing the DataSetWriter.
|
|
/// </returns>
|
|
public StatusCode RemoveDataSetReader(DataSetReaderDataType dataSetReaderDataType)
|
|
{
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
uint dataSetReaderId = FindIdForObject(dataSetReaderDataType);
|
|
if (dataSetReaderDataType != null && dataSetReaderId != InvalidId)
|
|
{
|
|
// find parent readerGroup
|
|
var parentWriterGroup = FindParentForObject(
|
|
dataSetReaderDataType) as ReaderGroupDataType;
|
|
// TODO: FindIdForObject throws if parentWriterGroup is null; null check below is unreachable.
|
|
uint parenReaderGroupId = FindIdForObject(parentWriterGroup!);
|
|
if (parentWriterGroup != null && parenReaderGroupId != InvalidId)
|
|
{
|
|
parentWriterGroup.DataSetReaders =
|
|
parentWriterGroup.DataSetReaders.RemoveItem(dataSetReaderDataType);
|
|
|
|
//remove all references from dictionaries
|
|
m_idsToObjects.Remove(dataSetReaderId);
|
|
m_objectsToIds.Remove(dataSetReaderDataType);
|
|
m_idsToParentId.Remove(dataSetReaderId);
|
|
m_idsToPubSubState.Remove(dataSetReaderId);
|
|
|
|
DataSetReaderRemoved?.Invoke(
|
|
this,
|
|
new DataSetReaderEventArgs
|
|
{
|
|
ReaderGroupId = parenReaderGroupId,
|
|
DataSetReaderDataType = dataSetReaderDataType,
|
|
DataSetReaderId = dataSetReaderId
|
|
});
|
|
return StatusCodes.Good;
|
|
}
|
|
}
|
|
return StatusCodes.BadNodeIdUnknown;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Unexpected exception
|
|
m_logger.LogError(ex, "UaPubSubConfigurator.RemoveDataSetReader: Exception");
|
|
}
|
|
|
|
return StatusCodes.BadInvalidArgument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable the specified configuration object specified by Id
|
|
/// </summary>
|
|
public StatusCode Enable(uint configurationId)
|
|
{
|
|
return Enable(FindObjectById(configurationId)!);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable the specified configuration object
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException"><paramref name="configurationObject"/></exception>
|
|
public StatusCode Enable(object configurationObject)
|
|
{
|
|
if (configurationObject is null)
|
|
{
|
|
throw new ArgumentException(
|
|
"The parameter cannot be null.",
|
|
nameof(configurationObject));
|
|
}
|
|
if (!m_objectsToIds.ContainsKey(configurationObject))
|
|
{
|
|
throw new ArgumentException(
|
|
"This {0} instance is not part of current configuration.",
|
|
configurationObject.GetType().Name);
|
|
}
|
|
PubSubState currentState = FindStateForObject(configurationObject);
|
|
if (currentState != PubSubState.Disabled)
|
|
{
|
|
m_logger.LogInformation(
|
|
"Attempted to call Enable() on an object that is not in Disabled state");
|
|
return StatusCodes.BadInvalidState;
|
|
}
|
|
PubSubState parentState = PubSubState.Operational;
|
|
if (!ReferenceEquals(configurationObject, PubSubConfiguration))
|
|
{
|
|
parentState = FindStateForObject(FindParentForObject(configurationObject)!);
|
|
}
|
|
|
|
if (parentState == PubSubState.Operational)
|
|
{
|
|
// Enabled and parent Operational
|
|
SetStateForObject(configurationObject, PubSubState.Operational);
|
|
}
|
|
else
|
|
{
|
|
// Enabled but parent not Operational
|
|
SetStateForObject(configurationObject, PubSubState.Paused);
|
|
}
|
|
UpdateChildrenState(configurationObject);
|
|
return StatusCodes.Good;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disable the specified configuration object specified by Id
|
|
/// </summary>
|
|
public StatusCode Disable(uint configurationId)
|
|
{
|
|
return Disable(FindObjectById(configurationId)!);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disable the specified configuration object
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException"><paramref name="configurationObject"/></exception>
|
|
public StatusCode Disable(object configurationObject)
|
|
{
|
|
if (configurationObject is null)
|
|
{
|
|
throw new ArgumentException(
|
|
"The parameter cannot be null.",
|
|
nameof(configurationObject));
|
|
}
|
|
if (!m_objectsToIds.ContainsKey(configurationObject))
|
|
{
|
|
throw new ArgumentException(
|
|
"This {0} instance is not part of current configuration.",
|
|
configurationObject.GetType().Name);
|
|
}
|
|
PubSubState currentState = FindStateForObject(configurationObject);
|
|
if (currentState == PubSubState.Disabled)
|
|
{
|
|
m_logger.LogInformation(
|
|
Utils.TraceMasks.Information,
|
|
"Attempted to call Disable() on an object that is already in Disabled state");
|
|
return StatusCodes.BadInvalidState;
|
|
}
|
|
|
|
SetStateForObject(configurationObject, PubSubState.Disabled);
|
|
|
|
UpdateChildrenState(configurationObject);
|
|
return StatusCodes.Good;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change state for the specified configuration object
|
|
/// </summary>
|
|
private void SetStateForObject(object configurationObject, PubSubState newState)
|
|
{
|
|
uint id = FindIdForObject(configurationObject);
|
|
if (id != InvalidId && m_idsToPubSubState.TryGetValue(id, out PubSubState oldState))
|
|
{
|
|
m_idsToPubSubState[id] = newState;
|
|
PubSubStateChanged?.Invoke(
|
|
this,
|
|
new PubSubStateChangedEventArgs
|
|
{
|
|
ConfigurationObject = configurationObject,
|
|
ConfigurationObjectId = id,
|
|
NewState = newState,
|
|
OldState = oldState
|
|
});
|
|
bool configurationObjectEnabled
|
|
= newState is PubSubState.Operational or PubSubState.Paused;
|
|
//update the Enabled flag in config object
|
|
switch (configurationObject)
|
|
{
|
|
case PubSubConfigurationDataType:
|
|
((PubSubConfigurationDataType)configurationObject).Enabled
|
|
= configurationObjectEnabled;
|
|
break;
|
|
case PubSubConnectionDataType:
|
|
((PubSubConnectionDataType)configurationObject).Enabled
|
|
= configurationObjectEnabled;
|
|
break;
|
|
case WriterGroupDataType:
|
|
((WriterGroupDataType)configurationObject).Enabled
|
|
= configurationObjectEnabled;
|
|
break;
|
|
case DataSetWriterDataType:
|
|
((DataSetWriterDataType)configurationObject).Enabled
|
|
= configurationObjectEnabled;
|
|
break;
|
|
case ReaderGroupDataType:
|
|
((ReaderGroupDataType)configurationObject).Enabled
|
|
= configurationObjectEnabled;
|
|
break;
|
|
case DataSetReaderDataType:
|
|
((DataSetReaderDataType)configurationObject).Enabled
|
|
= configurationObjectEnabled;
|
|
break;
|
|
default:
|
|
Debug.Fail("Unexpected type of configuration object");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate and update the state for child objects of a configuration object (StATE MACHINE)
|
|
/// </summary>
|
|
private void UpdateChildrenState(object configurationObject)
|
|
{
|
|
PubSubState parentState = FindStateForObject(configurationObject);
|
|
//find child ids
|
|
List<uint> childrenIds = FindChildrenIdsForObject(configurationObject);
|
|
if (parentState == PubSubState.Operational)
|
|
{
|
|
// Enabled and parent Operational
|
|
foreach (uint childId in childrenIds)
|
|
{
|
|
PubSubState childState = FindStateForId(childId);
|
|
if (childState == PubSubState.Paused)
|
|
{
|
|
// become Operational if Parent changed to Operational
|
|
object childObject = FindObjectById(childId)!;
|
|
SetStateForObject(childObject, PubSubState.Operational);
|
|
|
|
UpdateChildrenState(childObject);
|
|
}
|
|
}
|
|
}
|
|
else if (parentState is PubSubState.Disabled or PubSubState.Paused)
|
|
{
|
|
// Parent changed to Disabled or Paused
|
|
foreach (uint childId in childrenIds)
|
|
{
|
|
PubSubState childState = FindStateForId(childId);
|
|
if (childState is PubSubState.Operational or PubSubState.Error)
|
|
{
|
|
// become Operational if Parent changed to Operational
|
|
object childObject = FindObjectById(childId)!;
|
|
SetStateForObject(childObject, PubSubState.Paused);
|
|
|
|
UpdateChildrenState(childObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get <see cref="PubSubState"/> for an item depending on enabled flag and parent's <see cref="PubSubState"/>.
|
|
/// </summary>
|
|
/// <param name="enabled">Configured Enabled flag. </param>
|
|
/// <param name="parentPubSubState"><see cref="PubSubState"/> of the parent configured object.</param>
|
|
private static PubSubState GetInitialPubSubState(
|
|
bool enabled,
|
|
PubSubState parentPubSubState)
|
|
{
|
|
if (enabled)
|
|
{
|
|
if (parentPubSubState == PubSubState.Operational)
|
|
{
|
|
// The PubSub component is operational.
|
|
return PubSubState.Operational;
|
|
}
|
|
// The PubSub component is enabled but currently paused by a parent component. The
|
|
// parent component is either Disabled_0 or Paused_1.
|
|
return PubSubState.Paused;
|
|
}
|
|
// PubSub component is configured but currently disabled.
|
|
return PubSubState.Disabled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate and return the initial state of a pub sub data type configuration object
|
|
/// </summary>
|
|
private PubSubState GetInitialPubSubState(object configurationObject)
|
|
{
|
|
PubSubState parentPubSubState = PubSubState.Operational;
|
|
|
|
bool configurationObjectEnabled;
|
|
switch (configurationObject)
|
|
{
|
|
case PubSubConfigurationDataType:
|
|
configurationObjectEnabled = ((PubSubConfigurationDataType)configurationObject)
|
|
.Enabled;
|
|
break;
|
|
case PubSubConnectionDataType:
|
|
configurationObjectEnabled = ((PubSubConnectionDataType)configurationObject)
|
|
.Enabled;
|
|
//find parent state
|
|
parentPubSubState = FindStateForObject(PubSubConfiguration);
|
|
break;
|
|
case WriterGroupDataType:
|
|
{
|
|
configurationObjectEnabled = ((WriterGroupDataType)configurationObject).Enabled;
|
|
//find parent connection
|
|
object? parentConnection = FindParentForObject(configurationObject);
|
|
//find parent state
|
|
parentPubSubState = FindStateForObject(parentConnection!);
|
|
break;
|
|
}
|
|
case DataSetWriterDataType:
|
|
configurationObjectEnabled = ((DataSetWriterDataType)configurationObject)
|
|
.Enabled;
|
|
//find parent
|
|
object? parentWriterGroup = FindParentForObject(configurationObject);
|
|
//find parent state
|
|
parentPubSubState = FindStateForObject(parentWriterGroup!);
|
|
break;
|
|
case ReaderGroupDataType:
|
|
{
|
|
configurationObjectEnabled = ((ReaderGroupDataType)configurationObject).Enabled;
|
|
//find parent connection
|
|
object? parentConnection = FindParentForObject(configurationObject);
|
|
//find parent state
|
|
parentPubSubState = FindStateForObject(parentConnection!);
|
|
break;
|
|
}
|
|
case DataSetReaderDataType:
|
|
configurationObjectEnabled = ((DataSetReaderDataType)configurationObject)
|
|
.Enabled;
|
|
//find parent
|
|
object? parentReaderGroup = FindParentForObject(configurationObject);
|
|
//find parent state
|
|
parentPubSubState = FindStateForObject(parentReaderGroup!);
|
|
break;
|
|
default:
|
|
return PubSubState.Error;
|
|
}
|
|
return GetInitialPubSubState(configurationObjectEnabled, parentPubSubState);
|
|
}
|
|
}
|
|
}
|