Skip to content

Commit

Permalink
Feature/bad status nodes (Azure-Samples#33)
Browse files Browse the repository at this point in the history
* Nodes with Bad and Uncertain status code
 * add 2 nodes fast respective slow type that generate
* fix namespace issue when boyler not added
* fix namespace index order for backwards compatibility
* default slow nodes and fast nodes count to 1
* fix some crashes
* cleanup unused code

* increased version number

* update documentation
  • Loading branch information
cristipogacean authored Nov 20, 2020
1 parent a2a0e16 commit 6fed688
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 73 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ The following nodes are part of the PLC simulation:
- Sine wave with a dip anomaly
- Value showing a positive trend
- Value showing a negative trend
- Value having perodical good, bad and uncertain statuses (slow changing - 10s by default)
- Value having perodical good, bad and uncertain statuses (fast changing - 1s by default)

By default everything is enabled, use command line options to disable certain anomaly or data generation features.
Additionally to those nodes with simulated data, a JSON configuration file allows nodes to be created as specified. Finally, the simulation supports a number of nodes of specific types that can change at a configurable rate.
Expand Down Expand Up @@ -91,10 +93,10 @@ Here's a sample node configuration file:

## Slow and fast changing nodes
A number of changing nodes can be simulated with the following options. The nodes are categorized into slow and fast only for convenience.
- sn: Number of slow nodes
- sn: Number of slow nodes (default 1)
- sr: Rate in seconds at which to change the slow nodes (uint, default every 10 s)
- st: Data type for slow nodes (UInt|Double|Bool|UIntArray, case insensitive)
- fn: Number of fast nodes
- fn: Number of fast nodes (default 1)
- fr: Rate in seconds at which to change the fast nodes (uint, default every 1 s)
- ft: Data type for fast nodes (UInt|Double|Bool|UIntArray, case insensitive)

Expand Down
10 changes: 10 additions & 0 deletions src/Namespaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ namespace OpcPlc
/// </summary>
public static partial class Namespaces
{
/// <summary>
/// The namespace for the nodes provided by for boyler type.
/// </summary>
public const string OpcPlcBoiler = "http://microsoft.com/Opc/OpcPlc/Boiler";

/// <summary>
/// The namespace for the nodes provided by the for the boyler instance.
/// </summary>
public const string OpcPlcBoilerInstance = "http://microsoft.com/Opc/OpcPlc/BoilerInstance";

/// <summary>
/// The namespace for the nodes provided by the plc server.
/// </summary>
Expand Down
160 changes: 92 additions & 68 deletions src/PlcNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,42 @@ public uint StepUp
#endregion

public PlcNodeManager(IServerInternal server, ApplicationConfiguration configuration, string nodeFileName = null)
: base(server, configuration, Namespaces.OpcPlcApplications)
: base(server, configuration, new string[] { Namespaces.OpcPlcApplications, Namespaces.OpcPlcBoiler, Namespaces.OpcPlcBoilerInstance, })
{
_nodeFileName = nodeFileName;
SystemContext.NodeIdFactory = this;

SetComplexTypeNamespaces();
}

#pragma warning disable IDE0060 // Remove unused parameter
public void IncreaseSlowNodes(object state)
#pragma warning restore IDE0060 // Remove unused parameter
{
IncreaseNodes(_slowNodes, PlcSimulation.SlowNodeType);
if (_slowNodes != null)
{
IncreaseNodes(_slowNodes, PlcSimulation.SlowNodeType, StatusCodes.Good, false);
}

if (_slowBadNodes != null)
{
(StatusCode status, bool addBadValue) = BadStatusSequence[_slowBadNodesCycle++ % BadStatusSequence.Length];
IncreaseNodes(_slowBadNodes, PlcSimulation.SlowNodeType, status, addBadValue);
}
}

#pragma warning disable IDE0060 // Remove unused parameter
public void IncreaseFastNodes(object state)
#pragma warning restore IDE0060 // Remove unused parameter
{
IncreaseNodes(_fastNodes, PlcSimulation.FastNodeType);
if (_fastNodes != null)
{
IncreaseNodes(_fastNodes, PlcSimulation.FastNodeType, StatusCodes.Good, false);
}

if (_fastBadNodes != null)
{
(StatusCode status, bool addBadValue) = BadStatusSequence[_fastBadNodesCycle++ % BadStatusSequence.Length];
IncreaseNodes(_fastBadNodes, PlcSimulation.FastNodeType, status, addBadValue);
}
}

#pragma warning disable IDE0060 // Remove unused parameter
Expand Down Expand Up @@ -178,6 +194,10 @@ public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> e
_slowNodes = CreateBaseLoadNodes(dataFolder, "Slow", PlcSimulation.SlowNodeCount, PlcSimulation.SlowNodeType);
_fastNodes = CreateBaseLoadNodes(dataFolder, "Fast", PlcSimulation.FastNodeCount, PlcSimulation.FastNodeType);

// Process Bad slow/fast nodes
_slowBadNodes = CreateBaseLoadNodes(dataFolder, "BadSlow", 1, PlcSimulation.SlowNodeType);
_fastBadNodes = CreateBaseLoadNodes(dataFolder, "BadFast", 1, PlcSimulation.FastNodeType);

FolderState methodsFolder = CreateFolder(root, "Methods", "Methods", NamespaceType.OpcPlcApplications);
if (PlcSimulation.GeneratePosTrend || PlcSimulation.GenerateNegTrend)
{
Expand Down Expand Up @@ -256,41 +276,60 @@ public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> e
}
catch (Exception e)
{
Utils.Trace(e, "Error creating the address space.");
Logger.Error(e, "Error creating the address space.");
}

AddPredefinedNode(SystemContext, root);
}
}

private void IncreaseNodes(BaseDataVariableState[] nodes, NodeType type)
private void IncreaseNodes(BaseDataVariableState[] nodes, NodeType type, StatusCode status, bool addBadValue)
{
if (nodes == null || nodes.Length == 0) {
Logger.Warning("Invalid argument {argument} provided.", nodes);
return;
}
for (int nodeIndex = 0; nodeIndex < nodes.Length; nodeIndex++)
{
object value;

switch (type)
object value = null;
if (StatusCode.IsNotBad(status) || addBadValue)
{
case NodeType.Double:
value = (double)nodes[nodeIndex].Value + 0.1;
break;
case NodeType.Bool:
value = !(bool)nodes[nodeIndex].Value;
break;
case NodeType.UIntArray:
uint[] arrayValue = (uint[])nodes[nodeIndex].Value;
for (int arrayIndex = 0; arrayIndex < arrayValue?.Length; arrayIndex++)
{
arrayValue[arrayIndex]++;
}
value = arrayValue;
break;
case NodeType.UInt:
default:
value = (uint)nodes[nodeIndex].Value + 1;
break;
switch (type) {
case NodeType.Double:
value = nodes[nodeIndex].Value != null
? (double)nodes[nodeIndex].Value + 0.1
: 0.0;
break;
case NodeType.Bool:
value = nodes[nodeIndex].Value != null
? !(bool)nodes[nodeIndex].Value
: true;
break;
case NodeType.UIntArray:
uint[] arrayValue = (uint[])nodes[nodeIndex].Value;
if (arrayValue != null)
{
for (int arrayIndex = 0; arrayIndex < arrayValue?.Length; arrayIndex++)
{
arrayValue[arrayIndex]++;
}
}
else
{
arrayValue = new uint[32];
}
value = arrayValue;
break;
case NodeType.UInt:
default:
value = nodes[nodeIndex].Value != null
? (uint)nodes[nodeIndex].Value + 1
: 0;
break;
}
}

nodes[nodeIndex].StatusCode = status;
nodes[nodeIndex].Value = value;
nodes[nodeIndex].Timestamp = DateTime.Now;
nodes[nodeIndex].ClearChangeMasks(SystemContext, false);
Expand Down Expand Up @@ -329,8 +368,8 @@ private BaseDataVariableState[] CreateBaseLoadNodes(FolderState dataFolder, stri
if (count > 0)
{
Logger.Information($"Creating {count} {name} nodes of type: {type}");
Logger.Information("Node values will change every " + (name == "Fast" ? PlcSimulation.FastNodeRate : PlcSimulation.SlowNodeRate) + " s");
Logger.Information("Node values sampling rate is " + (name == "Fast" ? PlcSimulation.FastNodeSamplingInterval : PlcSimulation.SlowNodeSamplingInterval) + " ms");
Logger.Information("Node values will change every " + (name.Contains("Fast") ? PlcSimulation.FastNodeRate : PlcSimulation.SlowNodeRate) + " s");
Logger.Information("Node values sampling rate is " + (name.Contains("Fast") ? PlcSimulation.FastNodeSamplingInterval : PlcSimulation.SlowNodeSamplingInterval) + " ms");
}

for (int i = 0; i < count; i++)
Expand All @@ -348,10 +387,10 @@ private static (NodeId dataType, int valueRank, object defaultValue) GetNodeType
{
return nodeType switch
{
NodeType.Bool => (new NodeId((uint)BuiltInType.Boolean), ValueRanks.Scalar, null),
NodeType.Double => (new NodeId((uint)BuiltInType.Double), ValueRanks.Scalar, null),
NodeType.Bool => (new NodeId((uint)BuiltInType.Boolean), ValueRanks.Scalar, true),
NodeType.Double => (new NodeId((uint)BuiltInType.Double), ValueRanks.Scalar, (double)0.0),
NodeType.UIntArray => (new NodeId((uint)BuiltInType.UInt32), ValueRanks.OneDimension, new uint[32]),
_ => (new NodeId((uint)BuiltInType.UInt32), ValueRanks.Scalar, null),
_ => (new NodeId((uint)BuiltInType.UInt32), ValueRanks.Scalar, (uint)0),
};
}

Expand Down Expand Up @@ -481,17 +520,6 @@ private void CreateBaseVariable(NodeState parent, ConfigNode node)
CreateBaseVariable(parent, node.NodeId, node.Name, new NodeId((uint)nodeDataType), node.ValueRank, accessLevel, node.Description, NamespaceType.OpcPlcApplications);
}

private void SetComplexTypeNamespaces()
{
// Set one namespace for the type model and one names for dynamically created nodes.
var namespaceUrls = new string[3];
namespaceUrls[(int)NamespaceType.Boiler] = BoilerModel.Namespaces.Boiler;
namespaceUrls[(int)NamespaceType.BoilerInstance] = BoilerModel.Namespaces.Boiler + "/Instance";
namespaceUrls[(int)NamespaceType.OpcPlcApplications] = Namespaces.OpcPlcApplications;

SetNamespaces(namespaceUrls);
}

/// <summary>
/// Loads a node set from a file or resource and addes them to the set of predefined nodes.
/// </summary>
Expand Down Expand Up @@ -686,29 +714,6 @@ private MethodState CreateMethod(NodeState parent, string path, string name, str
return method;
}

/// <summary>
/// Creates a new method using type Numeric for the NodeId.
/// </summary>
private MethodState CreateMethod(NodeState parent, uint id, string name, ushort namespaceIndex)
{
var 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,
};

parent?.AddChild(method);

return method;
}

/// <summary>
/// Method to reset the trend values. Executes synchronously.
/// </summary>
Expand Down Expand Up @@ -778,11 +783,28 @@ private void SetValue<T>(BaseDataVariableState variable, T value)

private enum NamespaceType
{
OpcPlcApplications,
Boiler,
BoilerInstance,
OpcPlcApplications,
}

private (StatusCode, bool)[] BadStatusSequence = new (StatusCode, bool)[]
{
( StatusCodes.Good, true ),
( StatusCodes.Good, true ),
( StatusCodes.Good, true ),
( StatusCodes.UncertainLastUsableValue, true),
( StatusCodes.Good, true ),
( StatusCodes.Good, true ),
( StatusCodes.Good, true ),
( StatusCodes.UncertainLastUsableValue, true),
( StatusCodes.BadDataLost, true),
( StatusCodes.BadNoCommunication, false)
};

private uint _slowBadNodesCycle = 0;
private uint _fastBadNodesCycle = 0;

/// <summary>
/// Following variables listed here are simulated.
/// </summary>
Expand All @@ -797,6 +819,8 @@ private enum NamespaceType
protected BaseDataVariableState[] _slowNodes = null;
protected BaseDataVariableState[] _fastNodes = null;
protected BoilerModel.BoilerState _boiler1 = null;
protected BaseDataVariableState[] _slowBadNodes = null;
protected BaseDataVariableState[] _fastBadNodes = null;

/// <summary>
/// File name for user configurable nodes.
Expand Down
6 changes: 4 additions & 2 deletions src/PlcSimulation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ public class PlcSimulation
public static bool GeneratePosTrend { get; set; } = true;
public static bool GenerateNegTrend { get; set; } = true;
public static bool GenerateData { get; set; } = true;
public static uint SlowNodeCount { get; set; } = 0;
public static uint SlowNodeCount { get; set; } = 1;
public static uint SlowNodeRate { get; set; } = 10; // s.
public static NodeType SlowNodeType { get; set; } = NodeType.UInt;
public static uint SlowNodeSamplingInterval { get; set; } // ms.
public static uint FastNodeCount { get; set; } = 0;
public static uint FastNodeCount { get; set; } = 1;
public static uint FastNodeRate { get; set; } = 1; // s.
public static NodeType FastNodeType { get; set; } = NodeType.UInt;
public static uint FastNodeSamplingInterval { get; set; } // ms.
Expand Down Expand Up @@ -304,6 +304,8 @@ public void StopStepUp()

private Timer _slowNodeGenerator;
private Timer _fastNodeGenerator;

private Timer _boiler1Generator;

}
}
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.2.0",
"version": "1.2.1",
"versionHeightOffset": -1,
"publicReleaseRefSpec": [
"^refs/heads/master$",
Expand Down

0 comments on commit 6fed688

Please sign in to comment.