Skip to content

Commit

Permalink
Add very fast 1 kB nodes (#334)
Browse files Browse the repository at this point in the history
* Cosmetic

* Reformat

* Add VeryFast1KBPluginNodes

* Fix FlatDirectory and add vf1k
  • Loading branch information
luiscantero authored Dec 18, 2023
1 parent 964199c commit cca62fb
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 31 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ or if one string contains commas:
Options:
--lf, --logfile=VALUE the filename of the logfile to use.
Default: './hostname-plc.log'
Default: './hostname-port-plc.log'
--lt, --logflushtimespan=VALUE
the timespan in seconds when the logfile should be
flushed.
Expand Down Expand Up @@ -573,4 +573,10 @@ Options:
--nf, --nodesfile=VALUE
the filename that contains the list of nodes to be
created in the OPC UA address space.
--vf1k, --veryfast1knodes=VALUE
number of very fast 1 kB nodes.
Default: 1
--vf1kr, --veryfast1krate=VALUE
rate in ms to change very fast 1 kB nodes.
Default: 1000
```
4 changes: 2 additions & 2 deletions src/CliOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public static (Configuration Config, OpcApplicationConfiguration OpcUaConfig, Pl
{ "mqrc|maxqueuedrequestcount=", $"maximum number of requests that will be queued waiting for a thread.\nDefault: {opcUaConfig.MaxQueuedRequestCount}", (int i) => opcUaConfig.MaxQueuedRequestCount = i },

// cert store options
{ "at|appcertstoretype=", $"the own application cert store type.\n(allowed values: Directory, X509Store)\nDefault: '{opcUaConfig.OpcOwnCertStoreType}'", (string s) => {
{ "at|appcertstoretype=", $"the own application cert store type.\n(allowed values: Directory, X509Store, FlatDirectory)\nDefault: '{opcUaConfig.OpcOwnCertStoreType}'", (string s) => {
switch (s)
{
case CertificateStoreType.X509Store:
Expand All @@ -122,7 +122,7 @@ public static (Configuration Config, OpcApplicationConfiguration OpcUaConfig, Pl

{ "ap|appcertstorepath=", "the path where the own application cert should be stored.\nDefault (depends on store type):\n" +
$"X509Store: '{opcUaConfig.OpcOwnCertX509StorePathDefault}'\n" +
$"Directory: '{opcUaConfig.OpcOwnCertDirectoryStorePathDefault}'" +
$"Directory: '{opcUaConfig.OpcOwnCertDirectoryStorePathDefault}'\n" +
$"FlatDirectory: '{opcUaConfig.OpcOwnCertDirectoryStorePathDefault}'",
(string s) => opcUaConfig.OpcOwnCertStorePath = s
},
Expand Down
12 changes: 5 additions & 7 deletions src/PluginNodes/FastPluginNodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ public void StartSimulation()
{
// Only use the fast timers when we need to go really fast,
// since they consume more resources and create an own thread.
_nodeGenerator = NodeRate >= 50 || !Stopwatch.IsHighResolution ?
TimeService.NewTimer(UpdateNodes, NodeRate) :
TimeService.NewFastTimer(UpdateVeryFastNodes, intervalInMilliseconds: NodeRate);
_nodeGenerator = NodeRate >= 50 || !Stopwatch.IsHighResolution
? TimeService.NewTimer(UpdateNodes, NodeRate)
: TimeService.NewFastTimer(UpdateVeryFastNodes, intervalInMilliseconds: NodeRate);
}

public void StopSimulation()
Expand All @@ -156,8 +156,7 @@ private void ExposeNodesWithIntervals()

foreach (var node in _nodes)
{
nodes.Add(new NodeWithIntervals
{
nodes.Add(new NodeWithIntervals {
NodeId = node.NodeId.Identifier.ToString(),
Namespace = OpcPlc.Namespaces.OpcPlcApplications,
PublishingInterval = NodeRate,
Expand All @@ -167,8 +166,7 @@ private void ExposeNodesWithIntervals()

foreach (var node in _badNodes)
{
nodes.Add(new NodeWithIntervals
{
nodes.Add(new NodeWithIntervals {
NodeId = node.NodeId.Identifier.ToString(),
Namespace = OpcPlc.Namespaces.OpcPlcApplications,
PublishingInterval = NodeRate,
Expand Down
41 changes: 21 additions & 20 deletions src/PluginNodes/SlowFastCommon.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace OpcPlc.PluginNodes;
namespace OpcPlc.PluginNodes;

using Microsoft.Extensions.Logging;
using Opc.Ua;
Expand All @@ -8,7 +8,7 @@
public class SlowFastCommon
{
private readonly PlcNodeManager _plcNodeManager;
private readonly Random _random = new Random();
private readonly Random _random = new();
private BaseDataVariableState _numberOfUpdates;
private uint _badNodesCycle = 0;
private const string NumberOfUpdates = "NumberOfUpdates";
Expand All @@ -24,7 +24,8 @@ public SlowFastCommon(PlcNodeManager plcNodeManager)

uint badNodesCount = count == 0u
? 0u
: 1u;
: 1u;

var badNodes = CreateBaseLoadNodes(folder, $"Bad{name}", badNodesCount, nodeType, nodeRandomization, nodeStepSize, nodeMinValue, nodeMaxValue, nodeRate, nodeSamplingInterval);

_numberOfUpdates = CreateNumberOfUpdatesVariable(name, simulatorFolder);
Expand Down Expand Up @@ -168,7 +169,7 @@ private void UpdateNodes(BaseDataVariableState[] nodes, NodeType type, StatusCod
var value1 = _random.NextDouble() * maxDoubleValue;
var value2 = _random.NextDouble() * minDoubleValue;

// Return random value from postive or negative range, randomly.
// Return random value from positive or negative range, randomly.
value = _random.Next(10) % 2 == 0 ? value1 : value2;
}
}
Expand All @@ -192,22 +193,22 @@ private void UpdateNodes(BaseDataVariableState[] nodes, NodeType type, StatusCod
if (minDoubleValue >= 0 && maxDoubleValue > 0)
{
value = (extendedDoubleNodeValue % maxDoubleValue) < minDoubleValue
? minDoubleValue
: ((extendedDoubleNodeValue % maxDoubleValue) + (double)extendedNode.StepSize) > maxDoubleValue
? minDoubleValue
: ((extendedDoubleNodeValue % maxDoubleValue) + (double)extendedNode.StepSize);
? minDoubleValue
: ((extendedDoubleNodeValue % maxDoubleValue) + (double)extendedNode.StepSize) > maxDoubleValue
? minDoubleValue
: ((extendedDoubleNodeValue % maxDoubleValue) + (double)extendedNode.StepSize);
}
else if (maxDoubleValue <= 0 && minDoubleValue < 0) // Negative only range cases (e.g. 0 to -9.5).
else if (maxDoubleValue <= 0 && minDoubleValue < 0) // Negative-only range cases (e.g. 0 to -9.5).
{
value = (extendedDoubleNodeValue % minDoubleValue) > maxDoubleValue
? maxDoubleValue
: ((extendedDoubleNodeValue % minDoubleValue) - (double)extendedNode.StepSize) < minDoubleValue
? maxDoubleValue
: (extendedDoubleNodeValue % minDoubleValue) - (double)extendedNode.StepSize;
? maxDoubleValue
: ((extendedDoubleNodeValue % minDoubleValue) - (double)extendedNode.StepSize) < minDoubleValue
? maxDoubleValue
: (extendedDoubleNodeValue % minDoubleValue) - (double)extendedNode.StepSize;
}
else
{
// This is to prevent infinte loop while attempting to create a different random number than previous one if no range is provided.
// This is to prevent infinite loop while attempting to create a different random number than previous one if no range is provided.
throw new ArgumentException($"Negative to positive range {minDoubleValue} to {maxDoubleValue} for sequential node values is not supported currently.");
}
}
Expand Down Expand Up @@ -246,23 +247,23 @@ private void UpdateNodes(BaseDataVariableState[] nodes, NodeType type, StatusCod
// If new random value is same as previous one, generate a new one until it is not.
while (value == null || extendedUIntNodeValue == (uint)value)
{
// uint.MaxValue + 1 cycles back to 0 which causes infinte loop here hence a check maxUIntValue == uint.MaxValue to prevent it.
// uint.MaxValue + 1 cycles back to 0 which causes infinite loop here hence a check maxUIntValue == uint.MaxValue to prevent it.
value = (uint)(minUIntValue + (_random.NextDouble() * ((maxUIntValue == uint.MaxValue ? maxUIntValue : maxUIntValue + 1) - minUIntValue)));
}
}
else
{
// This is to prevent infinte loop while attempting to create a different random number than previous one if no range is provided.
// This is to prevent infinite loop while attempting to create a different random number than previous one if no range is provided.
throw new ArgumentException($"Range {minUIntValue} to {maxUIntValue} does not have provision for randomness.");
}
}
else
{
value = (extendedUIntNodeValue % maxUIntValue) < minUIntValue
? minUIntValue
: ((extendedUIntNodeValue % maxUIntValue) + (uint)extendedNode.StepSize) > maxUIntValue
? minUIntValue
: ((extendedUIntNodeValue % maxUIntValue) + (uint)extendedNode.StepSize);
? minUIntValue
: ((extendedUIntNodeValue % maxUIntValue) + (uint)extendedNode.StepSize) > maxUIntValue
? minUIntValue
: ((extendedUIntNodeValue % maxUIntValue) + (uint)extendedNode.StepSize);
}

break;
Expand Down
142 changes: 142 additions & 0 deletions src/PluginNodes/VeryFast1KBPluginNodes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
namespace OpcPlc.PluginNodes;

using Opc.Ua;
using OpcPlc.Helpers;
using OpcPlc.PluginNodes.Models;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using static OpcPlc.Program;

/// <summary>
/// Nodes with 1 kB values that change in ms cycles.
/// The values are scrambled to ensure that they are efficiently compressed.
/// </summary>
public class VeryFast1KBPluginNodes : IPluginNodes
{
public IReadOnlyCollection<NodeWithIntervals> Nodes { get; private set; } = new List<NodeWithIntervals>();

private uint NodeCount { get; set; } = 1;
private uint NodeRate { get; set; } = 1000; // ms.

private readonly DeterministicGuid _deterministicGuid = new();
private PlcNodeManager _plcNodeManager;
private BaseDataVariableState[] _veryFast1KBNodes;
private ITimer _nodeGenerator;

public void AddOptions(Mono.Options.OptionSet optionSet)
{
optionSet.Add(
"vf1k|veryfast1knodes=",
$"number of very fast 1 kB nodes.\nDefault: {NodeCount}",
(uint i) => NodeCount = i);

optionSet.Add(
"vf1kr|veryfast1krate=",
$"rate in ms to change very fast 1 kB nodes.\nDefault: {NodeRate}",
(uint i) => NodeRate = i);
}

public void AddToAddressSpace(FolderState telemetryFolder, FolderState methodsFolder, PlcNodeManager plcNodeManager)
{
_plcNodeManager = plcNodeManager;

if (NodeCount == 0)
{
return;
}

FolderState folder = _plcNodeManager.CreateFolder(
telemetryFolder,
path: "VeryFast1kB",
name: "VeryFast1kB",
NamespaceType.OpcPlcApplications);

AddNodes(folder);
}

public void StartSimulation()
{
// Only use the fast timers when we need to go really fast,
// since they consume more resources and create an own thread.
_nodeGenerator = NodeRate >= 50 || !Stopwatch.IsHighResolution
? TimeService.NewTimer((s, e) => UpdateNodes(), NodeRate)
: TimeService.NewFastTimer((s, e) => UpdateNodes(), intervalInMilliseconds: NodeRate);
}

public void StopSimulation()
{
if (_nodeGenerator != null)
{
_nodeGenerator.Enabled = false;
}
}

private void AddNodes(FolderState folder)
{
var nodes = new List<NodeWithIntervals>();
_veryFast1KBNodes = new BaseDataVariableState[NodeCount];

for (int i = 0; i < NodeCount; i++)
{
string oneKbGuid = GetLongDeterministicGuid(maxLength: 1024);
var initialByteArray = Encoding.UTF8.GetBytes(oneKbGuid);

string name = $"VeryFast1kB{(i + 1)}";

_veryFast1KBNodes[i] = _plcNodeManager.CreateBaseVariable(
folder,
path: name,
name: name,
new NodeId((uint)BuiltInType.ByteString),
ValueRanks.Scalar,
AccessLevels.CurrentReadOrWrite,
"Very fast changing 1 kB node",
NamespaceType.OpcPlcApplications,
initialByteArray);

// Update pn.json output.
nodes.Add(new NodeWithIntervals {
NodeId = name,
Namespace = OpcPlc.Namespaces.OpcPlcApplications,
PublishingInterval = NodeRate,
});

Nodes = nodes;
}
}

private void UpdateNodes()
{
for (int i = 0; i < _veryFast1KBNodes.Length; i++)
{
byte[] arrayValue = (byte[])_veryFast1KBNodes[i].Value;

// Update first byte in the range 0 to 255.
arrayValue[0] = arrayValue[0] == 255
? (byte)0
: (byte)(arrayValue[0] + 1);

SetValue(_veryFast1KBNodes[i], arrayValue);
}
}

private void SetValue<T>(BaseVariableState variable, T value)
{
variable.Value = value;
variable.Timestamp = TimeService.Now();
variable.ClearChangeMasks(_plcNodeManager.SystemContext, includeChildren: false);
}

private string GetLongDeterministicGuid(int maxLength)
{
var sb = new StringBuilder();

while (sb.Length < maxLength)
{
sb.Append(_deterministicGuid.NewGuid().ToString());
}

return sb.ToString()[..maxLength];
}
}
2 changes: 1 addition & 1 deletion src/TimeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private void Runner()
{
double nextTrigger = 0f;

Stopwatch sw = new Stopwatch();
var sw = new Stopwatch();
sw.Start();

while (_isRunning == 1)
Expand Down

0 comments on commit cca62fb

Please sign in to comment.