Files
Marc Schier dbc09b8e22 Adopt System.TimeProvider across the stack (fixes #2458) (#3829)
# Description

Adopt
[`System.TimeProvider`](https://learn.microsoft.com/dotnet/api/system.timeprovider)
across the stack so that every place that creates timers, runs duration
math, or schedules periodic work is mockable, deterministic in tests,
and immune to wall-clock changes.

The migration touches Core, Core.Types, Server, Client, PubSub,
Configuration, Gds (server + client), Lds, and the Quickstarts/Console
reference apps. Per-component changes follow the same pattern:

1. Add a `private readonly TimeProvider m_timeProvider;` field.
2. Accept a nullable `TimeProvider? timeProvider = null` as the **last**
constructor parameter (new overload; legacy constructor stays for binary
compatibility and delegates with `timeProvider: null`).
3. Initialise the field with `m_timeProvider = timeProvider ??=
TimeProvider.System;`.
4. Replace `new Timer(...)`, `Stopwatch`, `HiResClock.*`,
`DateTime.UtcNow`-based duration math, and `Task.Delay(delay, ct)` in
timing loops with the matching `m_timeProvider` API (`CreateTimer`,
`GetTimestamp` / `GetElapsedTime`, `GetUtcNow().UtcDateTime`,
`Task.Delay(delay, m_timeProvider, ct)`).
5. Wire the provider through DI: `AddOpcUaServerBuilder` /
`AddOpcUaClientBuilder` register `TimeProvider.System` via
`TryAddSingleton<TimeProvider>()` so tests can swap in
`Microsoft.Extensions.Time.Testing.FakeTimeProvider`.

`HiResClock` itself is kept (so the
`ApplicationConfiguration.DisableHiResClock` schema flag still
round-trips), but **every public member** is now `[Obsolete]` with a
single shared message pointing to the new migration-guide section. The
few internal call sites that still need to set/read `HiResClock` are
wrapped with `#pragma warning disable CS0618`.

### Highlights per component

- **Core / Stack** — `Opc.Ua.Core`, `Opc.Ua.Core.Types`, channel
pipeline (`UaSCBinaryClientChannel`, `ChannelToken`,
`UaSCBinaryChannel`), `ApplicationConfiguration`, certificate
validators, `JwksIssuerKeyResolver`, `TimeProviderExtensions` polyfills
(`GetTimestampMilliseconds`, `GetTickCount`,
`CancellationTokenSource(timeout, timeProvider)`, `Task.Delay(delay,
provider, ct)` polyfill for older TFMs).
- **Server** — session lockout, subscription pipeline
(publish/sample/keep-alive/lifetime), `MonitoredItemQueue`, alarm
sampling rate, `IntervalRunner`, server diagnostics, configuration state
machines, hosted services.
- **Client** — `Session`, managed/V2 subscription engines, reconnect
handlers, channel keep-alive, periodic publishing, complex-type
discovery polling.
- **PubSub** — `IntervalRunner` pipeline, message-receive timers,
MQTT/UDP keep-alive, publishers/readers.
- **Gds / Lds** — `LdsServer`, `LinqApplicationsDatabase` expiry
housekeeping, GDS clients (push, pull), `DirectoryCertificateStore`,
`ConnectionStateMachine`, classic subscription engine.
- **Quickstarts / Console samples** — `SampleNodeManager`,
`ReferenceNodeManager`, `AlarmNodeManager`, `BoilerState`,
`MemoryBufferState`, `PublishedValuesWrites`. Samples resolve via
`(Server as ITimeProviderProvider)?.TimeProvider ?? TimeProvider.System`
so existing ctor signatures stay binary-compatible.
- **Tests** — `Opc.Ua.PubSub.Tests` adds `IntervalRunnerTests`; existing
`SubscriptionTests`, `UaPublisherTests`, and `TestDataSystem` move off
`HiResClock`. Added `Microsoft.Extensions.TimeProvider.Testing` package
for `FakeTimeProvider` (project-load warning surfaces on net48/net472
only — runtime is .NET 8+).

### Migration guide

New section [**Time and Timer abstraction
(`TimeProvider`)**](Docs/MigrationGuide.md#time-and-timer-abstraction-timeprovider)
documents:

- The canonical replacement table (`HiResClock.UtcNow` →
`timeProvider.GetUtcNow().UtcDateTime`, `HiResClock.TickCount64` →
`timeProvider.GetTimestamp()`, `new Timer` → `timeProvider.CreateTimer`,
`Task.Delay(delay, ct)` → `Task.Delay(delay, timeProvider, ct)`, …).
- The `TimeProvider? timeProvider = null` + `??=` constructor pattern.
- DI wiring and how to swap in `FakeTimeProvider` in tests.
- Before/after snippets for `HiResClock.TickCount64`,
`HiResClock.UtcNow`, and `new Timer(...)`.

## Related Issues

- Fixes #2458

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
2026-06-03 22:38:32 +02:00

5606 lines
248 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.Globalization;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Server;
using Opc.Ua.Server.Historian;
using Opc.Ua.Server.Historian.InMemory;
using Opc.Ua.Test;
using Range = Opc.Ua.Range;
namespace Quickstarts.ReferenceServer
{
/// <summary>
/// A node manager for a server that exposes several variables.
/// </summary>
public class ReferenceNodeManager : AsyncCustomNodeManager
{
/// <summary>
/// Initializes the node manager.
/// </summary>
public ReferenceNodeManager(
IServerInternal server,
ApplicationConfiguration configuration,
bool useSamplingGroups = false)
: base(
server,
configuration,
useSamplingGroups,
server.Telemetry.CreateLogger<ReferenceNodeManager>(),
Namespaces.ReferenceServer)
{
SystemContext.NodeIdFactory = this;
// use suitable defaults if no configuration exists.
}
/// <summary>
/// An overrideable version of the Dispose.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Dispose the simulation timer first so the threadpool stops
// scheduling DoSimulation callbacks before the semaphore is
// disposed. DoSimulation's own try/catch swallows the racy
// ObjectDisposedException on m_semaphore if a callback was
// already in-flight when Timer.Dispose() returned — that's
// an acceptable trade-off versus blocking Dispose on a
// Timer.Dispose(WaitHandle) which itself can throw a worse
// unhandled ObjectDisposedException when the supplied
// WaitHandle is collected before the runtime signals it.
m_simulationTimer?.Dispose();
m_simulationTimer = null;
m_historian?.Dispose();
m_historian = null;
m_semaphore?.Dispose();
}
base.Dispose(disposing);
}
/// <summary>
/// Creates the NodeId for the specified node.
/// </summary>
public override NodeId New(ISystemContext context, NodeState node)
{
if (node is BaseInstanceState instance &&
instance.Parent != null &&
instance.Parent.NodeId.TryGetValue(out string id))
{
return new NodeId(
id + "_" + instance.SymbolicName,
instance.Parent.NodeId.NamespaceIndex);
}
return node.NodeId;
}
/// <summary>
/// Adds a new instance node to the address space under a parent that may
/// belong to a different node manager. Used by the AddNodes service
/// implementation in <see cref="ReferenceServer"/> to allow the test
/// fixture to exercise the Node Management service set.
/// </summary>
/// <remarks>
/// The node is registered in this manager's namespace, an inverse
/// reference back to the parent is attached, and a forward reference
/// from the parent to the new node is added through the master node
/// manager so the parent's node manager records the link as well.
/// </remarks>
public async ValueTask<NodeId> AddInstanceNodeAsync(
ServerSystemContext context,
NodeId parentNodeId,
NodeId referenceTypeId,
BaseInstanceState instance,
CancellationToken cancellationToken = default)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
ServerSystemContext contextToUse = SystemContext.Copy(context);
if (instance.NodeId.IsNull)
{
instance.NodeId = new NodeId(
Guid.NewGuid().ToString(),
NamespaceIndexes[0]);
}
instance.ReferenceTypeId = referenceTypeId;
instance.AddReference(referenceTypeId, true, parentNodeId);
await AddPredefinedNodeAsync(contextToUse, instance, cancellationToken)
.ConfigureAwait(false);
var references = new List<IReference>
{
new NodeStateReference(referenceTypeId, false, instance.NodeId)
};
await Server.NodeManager.AddReferencesAsync(
parentNodeId,
references,
cancellationToken).ConfigureAwait(false);
return instance.NodeId;
}
private static bool IsAnalogType(BuiltInType builtInType)
{
switch (builtInType)
{
case BuiltInType.Byte:
case BuiltInType.UInt16:
case BuiltInType.UInt32:
case BuiltInType.UInt64:
case BuiltInType.SByte:
case BuiltInType.Int16:
case BuiltInType.Int32:
case BuiltInType.Int64:
case BuiltInType.Float:
case BuiltInType.Double:
return true;
case >= BuiltInType.Null and <= BuiltInType.Enumeration:
return false;
default:
Debug.Fail($"Unexpected BuiltInType {builtInType}");
return false;
}
}
private static Range GetAnalogRange(BuiltInType builtInType)
{
switch (builtInType)
{
case BuiltInType.UInt16:
return new Range(ushort.MaxValue, ushort.MinValue);
case BuiltInType.UInt32:
return new Range(uint.MaxValue, uint.MinValue);
case BuiltInType.UInt64:
return new Range(ulong.MaxValue, ulong.MinValue);
case BuiltInType.SByte:
return new Range(sbyte.MaxValue, sbyte.MinValue);
case BuiltInType.Int16:
return new Range(short.MaxValue, short.MinValue);
case BuiltInType.Int32:
return new Range(int.MaxValue, int.MinValue);
case BuiltInType.Int64:
return new Range(long.MaxValue, long.MinValue);
case BuiltInType.Float:
return new Range(float.MaxValue, float.MinValue);
case BuiltInType.Double:
return new Range(double.MaxValue, double.MinValue);
case BuiltInType.Byte:
return new Range(byte.MaxValue, byte.MinValue);
case >= BuiltInType.Null and <= BuiltInType.Enumeration:
return new Range(sbyte.MaxValue, sbyte.MinValue);
default:
Debug.Fail($"Unexpected BuiltInType {builtInType}");
return new Range(sbyte.MaxValue, sbyte.MinValue);
}
}
/// <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 async ValueTask CreateAddressSpaceAsync(
IDictionary<NodeId, IList<IReference>> externalReferences,
CancellationToken cancellationToken = default)
{
await m_semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (!externalReferences.TryGetValue(
ObjectIds.ObjectsFolder,
out IList<IReference>? references))
{
externalReferences[ObjectIds.ObjectsFolder] = references = [];
}
FolderState root = CreateFolder(null, "CTT", "CTT");
root.AddReference(ReferenceTypeIds.Organizes, true, ObjectIds.ObjectsFolder);
references.Add(
new NodeStateReference(ReferenceTypeIds.Organizes, false, root.NodeId));
root.EventNotifier = EventNotifiers.SubscribeToEvents;
await AddRootNotifierAsync(root, cancellationToken).ConfigureAwait(false);
var variables = new List<BaseDataVariableState>();
try
{
ResetRandomGenerator(1);
FolderState scalarFolder = CreateFolder(root, "Scalar", "Scalar");
BaseDataVariableState scalarInstructions = CreateVariable(
scalarFolder,
"Scalar_Instructions",
"Scalar_Instructions",
DataTypeIds.String,
ValueRanks.Scalar);
scalarInstructions.Value
= "A library of Read/Write Variables of all supported data-types.";
variables.Add(scalarInstructions);
FolderState staticFolder = CreateFolder(
scalarFolder,
"Scalar_Static",
"Scalar_Static");
const string scalarStatic = "Scalar_Static_";
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.Scalar));
BaseDataVariableState floatVal = CreateVariable(
staticFolder,
scalarStatic + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.Scalar)
.MinimumSamplingInterval(100);
floatVal.Value = (float)5;
variables.Add(floatVal);
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.Scalar));
BaseDataVariableState int32Static = CreateVariable(
staticFolder,
scalarStatic + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.Scalar);
// Expose RolePermissions / UserRolePermissions
// on the Int32 static scalar so the conformance attribute
// tests (AttributeReadComplexTests RolePermissions /
// UserRolePermissions read) return Good rather than
// BadAttributeIdInvalid. Anonymous users are granted
// Browse + Read + ReadRolePermissions; SecurityAdmin gets
// full permissions for write-attribute scenarios.
var anonPerms = new RolePermissionType
{
RoleId = ObjectIds.WellKnownRole_Anonymous,
Permissions =
(uint)PermissionType.Browse |
(uint)PermissionType.Read |
(uint)PermissionType.Write |
(uint)PermissionType.ReadRolePermissions
};
var adminPerms = new RolePermissionType
{
RoleId = ObjectIds.WellKnownRole_SecurityAdmin,
Permissions = 0xFFFF
};
int32Static.RolePermissions = new[] { anonPerms, adminPerms }.ToArrayOf();
int32Static.UserRolePermissions = new[] { anonPerms }.ToArrayOf();
variables.Add(int32Static);
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "LocaleId",
"LocaleId",
DataTypeIds.LocaleId,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "QualifiedName",
"QualifiedName",
DataTypeIds.QualifiedName,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "String",
"String",
DataTypeIds.String,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.Scalar)
.MinimumSamplingInterval(100));
BaseDataVariableState decimalVariable = CreateVariable(
staticFolder,
scalarStatic + "Decimal",
"Decimal",
DataTypeIds.DecimalDataType,
ValueRanks.Scalar);
// Set an arbitrary precision decimal value.
var largeInteger = BigInteger.Parse(
"1234567890123546789012345678901234567890123456789012345",
CultureInfo.InvariantCulture);
decimalVariable.Value = Variant.FromStructure(new DecimalDataType
{
Scale = 100,
Value = largeInteger.ToByteString()
});
variables.Add(decimalVariable);
// Enumeration variable (NodeClass is a concrete subtype of Enumeration)
BaseDataVariableState enumerationVariable = CreateVariable(
staticFolder,
scalarStatic + "Enumeration",
"Enumeration",
new NodeId(DataTypes.NodeClass),
ValueRanks.Scalar);
enumerationVariable.Value = new Variant((int)NodeClass.Object);
variables.Add(enumerationVariable);
// Image type variables (ByteString subtypes)
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "Image",
"Image",
new NodeId(DataTypes.Image),
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "ImageBMP",
"ImageBMP",
new NodeId(DataTypes.ImageBMP),
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "ImageGIF",
"ImageGIF",
new NodeId(DataTypes.ImageGIF),
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "ImageJPG",
"ImageJPG",
new NodeId(DataTypes.ImageJPG),
ValueRanks.Scalar));
variables.Add(
CreateVariable(
staticFolder,
scalarStatic + "ImagePNG",
"ImagePNG",
new NodeId(DataTypes.ImagePNG),
ValueRanks.Scalar));
ResetRandomGenerator(2);
FolderState arraysFolder = CreateFolder(
staticFolder,
"Scalar_Static_Arrays",
"Arrays");
const string staticArrays = "Scalar_Static_Arrays_";
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.OneDimension));
BaseDataVariableState doubleArrayVar = CreateVariable(
arraysFolder,
staticArrays + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.OneDimension);
// Set the first elements of the array to a smaller value.
double[] doubleArrayVal = ((ArrayOf<double>)doubleArrayVar.Value).ToArray()!;
doubleArrayVal[0] %= 10E+10;
doubleArrayVal[1] %= 10E+10;
doubleArrayVal[2] %= 10E+10;
doubleArrayVal[3] %= 10E+10;
doubleArrayVar.Value = Variant.From(doubleArrayVal.ToArrayOf());
variables.Add(doubleArrayVar);
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.OneDimension));
BaseDataVariableState floatArrayVar = CreateVariable(
arraysFolder,
staticArrays + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.OneDimension);
// Set the first elements of the array to a smaller value.
float[] floatArrayVal = ((ArrayOf<float>)floatArrayVar.Value).ToArray()!;
floatArrayVal[0] %= 0xf10E + 4;
floatArrayVal[1] %= 0xf10E + 4;
floatArrayVal[2] %= 0xf10E + 4;
floatArrayVal[3] %= 0xf10E + 4;
floatArrayVar.Value = Variant.From(floatArrayVal.ToArrayOf());
variables.Add(floatArrayVar);
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "LocaleId",
"LocaleId",
DataTypeIds.LocaleId,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "QualifiedName",
"QualifiedName",
DataTypeIds.QualifiedName,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.OneDimension));
BaseDataVariableState stringArrayVar = CreateVariable(
arraysFolder,
staticArrays + "String",
"String",
DataTypeIds.String,
ValueRanks.OneDimension);
stringArrayVar.Value = Variant.From(
[
"Лошадь_ Пурпурово( Змейка( Слон",
"猪 绿色 绵羊 大象~ 狗 菠萝 猪鼠猪 绿色 绵羊 大象~ 狗 菠萝 猪鼠",
"Лошадь Овцы Голубика Овцы Змейка",
"Чернота` Дракон Бело Дракон",
"Horse# Black Lemon Lemon Grape",
"猫< パイナップル; ドラゴン 犬 モモ",
"레몬} 빨간% 자주색 쥐 백색; 들",
"Yellow Sheep Peach Elephant Cow",
"Крыса Корова Свинья Собака Кот",
"龙_ 绵羊 大象 芒果; 猫'"
]);
variables.Add(stringArrayVar);
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.OneDimension));
variables.Add(
CreateVariable(
arraysFolder,
staticArrays + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.OneDimension));
ResetRandomGenerator(3);
FolderState arrays2DFolder = CreateFolder(
staticFolder,
"Scalar_Static_Arrays2D",
"Arrays2D");
const string staticArrays2D = "Scalar_Static_Arrays2D_";
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "LocaleId",
"LocaleId",
DataTypeIds.LocaleId,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.TwoDimensions)
.MinimumSamplingInterval(1000));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "QualifiedName",
"QualifiedName",
DataTypeIds.QualifiedName,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "String",
"String",
DataTypeIds.String,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.TwoDimensions));
variables.Add(
CreateVariable(
arrays2DFolder,
staticArrays2D + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.TwoDimensions)
.MinimumSamplingInterval(1000));
ResetRandomGenerator(4);
FolderState arrayDynamicFolder = CreateFolder(
staticFolder,
"Scalar_Static_ArrayDynamic",
"ArrayDynamic");
const string staticArraysDynamic = "Scalar_Static_ArrayDynamic_";
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "LocaleId",
"LocaleId",
DataTypeIds.LocaleId,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.OneOrMoreDimensions)
.MinimumSamplingInterval(1000));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "QualifiedName",
"QualifiedName",
DataTypeIds.QualifiedName,
ValueRanks.OneOrMoreDimensions)
.MinimumSamplingInterval(1000));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "String",
"String",
DataTypeIds.String,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.OneOrMoreDimensions));
variables.Add(
CreateVariable(
arrayDynamicFolder,
staticArraysDynamic + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.OneOrMoreDimensions)
.MinimumSamplingInterval(1000));
ResetRandomGenerator(5);
// create 100 instances of each static scalar type
FolderState massFolder = CreateFolder(
staticFolder,
"Scalar_Static_Mass",
"Mass");
const string staticMass = "Scalar_Static_Mass_";
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "String",
"String",
DataTypeIds.String,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.Scalar,
100));
variables.AddRange(
CreateVariables(
massFolder,
staticMass + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.Scalar,
100));
ResetRandomGenerator(6);
FolderState simulationFolder = CreateFolder(
scalarFolder,
"Scalar_Simulation",
"Simulation");
const string scalarSimulation = "Scalar_Simulation_";
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "LocaleId",
"LocaleId",
DataTypeIds.LocaleId,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "QualifiedName",
"QualifiedName",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "String",
"String",
DataTypeIds.String,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.Scalar);
CreateDynamicVariable(
simulationFolder,
scalarSimulation + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.Scalar);
BaseDataVariableState intervalVariable = CreateVariable(
simulationFolder,
scalarSimulation + "Interval",
"Interval",
DataTypeIds.UInt16,
ValueRanks.Scalar);
intervalVariable.Value = m_simulationInterval;
intervalVariable.OnSimpleWriteValue = OnWriteInterval;
BaseDataVariableState enabledVariable = CreateVariable(
simulationFolder,
scalarSimulation + "Enabled",
"Enabled",
DataTypeIds.Boolean,
ValueRanks.Scalar);
enabledVariable.Value = m_simulationEnabled;
enabledVariable.OnSimpleWriteValue = OnWriteEnabled;
ResetRandomGenerator(7);
FolderState arraysSimulationFolder = CreateFolder(
simulationFolder,
"Scalar_Simulation_Arrays",
"Arrays");
const string simulationArrays = "Scalar_Simulation_Arrays_";
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "LocaleId",
"LocaleId",
DataTypeIds.LocaleId,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "QualifiedName",
"QualifiedName",
DataTypeIds.QualifiedName,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "String",
"String",
DataTypeIds.String,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.OneDimension);
CreateDynamicVariable(
arraysSimulationFolder,
simulationArrays + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.OneDimension);
ResetRandomGenerator(8);
FolderState massSimulationFolder = CreateFolder(
simulationFolder,
"Scalar_Simulation_Mass",
"Mass");
const string massSimulation = "Scalar_Simulation_Mass_";
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Boolean",
"Boolean",
DataTypeIds.Boolean,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Byte",
"Byte",
DataTypeIds.Byte,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "ByteString",
"ByteString",
DataTypeIds.ByteString,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "DateTime",
"DateTime",
DataTypeIds.DateTime,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Double",
"Double",
DataTypeIds.Double,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Float",
"Float",
DataTypeIds.Float,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Guid",
"Guid",
DataTypeIds.Guid,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Int16",
"Int16",
DataTypeIds.Int16,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Int32",
"Int32",
DataTypeIds.Int32,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Int64",
"Int64",
DataTypeIds.Int64,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Integer",
"Integer",
DataTypeIds.Integer,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "LocaleId",
"LocaleId",
DataTypeIds.LocaleId,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "LocalizedText",
"LocalizedText",
DataTypeIds.LocalizedText,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "NodeId",
"NodeId",
DataTypeIds.NodeId,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Number",
"Number",
DataTypeIds.Number,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "QualifiedName",
"QualifiedName",
DataTypeIds.QualifiedName,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "SByte",
"SByte",
DataTypeIds.SByte,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "String",
"String",
DataTypeIds.String,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "UInt16",
"UInt16",
DataTypeIds.UInt16,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "UInt32",
"UInt32",
DataTypeIds.UInt32,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "UInt64",
"UInt64",
DataTypeIds.UInt64,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "UInteger",
"UInteger",
DataTypeIds.UInteger,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "UtcTime",
"UtcTime",
DataTypeIds.UtcTime,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "Variant",
"Variant",
BuiltInType.Variant,
ValueRanks.Scalar,
100);
CreateDynamicVariables(
massSimulationFolder,
massSimulation + "XmlElement",
"XmlElement",
DataTypeIds.XmlElement,
ValueRanks.Scalar,
100);
ResetRandomGenerator(9);
FolderState daFolder = CreateFolder(root, "DataAccess", "DataAccess");
BaseDataVariableState daInstructions = CreateVariable(
daFolder,
"DataAccess_Instructions",
"Instructions",
DataTypeIds.String,
ValueRanks.Scalar);
daInstructions.Value
= "A library of Read/Write Variables of all supported data-types.";
variables.Add(daInstructions);
FolderState dataItemFolder = CreateFolder(
daFolder,
"DataAccess_DataItem",
"DataItem");
const string daDataItem = "DataAccess_DataItem_";
#if NET8_0_OR_GREATER
BuiltInType[] builtInTypes = Enum.GetValues<BuiltInType>();
#else
var builtInTypes = (BuiltInType[])Enum.GetValues(typeof(BuiltInType));
#endif
foreach (BuiltInType builtInType in builtInTypes)
{
string name = builtInType.ToString();
DataItemState item = CreateDataItemVariable(
dataItemFolder,
daDataItem + name,
name,
builtInType,
ValueRanks.Scalar);
// set initial value to String.Empty for String node.
if (builtInType == BuiltInType.String)
{
item.Value = string.Empty;
}
}
ResetRandomGenerator(10);
FolderState analogItemFolder = CreateFolder(
daFolder,
"DataAccess_AnalogType",
"AnalogType");
const string daAnalogItem = "DataAccess_AnalogType_";
foreach (BuiltInType builtInType in builtInTypes)
{
if (IsAnalogType(builtInType))
{
string name = builtInType.ToString();
AnalogItemState item = CreateAnalogItemVariable(
analogItemFolder,
daAnalogItem + name,
name,
builtInType,
ValueRanks.Scalar);
if (builtInType is BuiltInType.Int64 or BuiltInType.UInt64)
{
// make test case without optional ranges
item.EngineeringUnits = null;
item.InstrumentRange = null;
}
else if (builtInType == BuiltInType.Float)
{
item.EURange!.Value.High = 0;
item.EURange.Value.Low = 0;
}
//set default value for Definition property
item.Definition?.Value = string.Empty;
}
}
ResetRandomGenerator(11);
FolderState analogArrayFolder = CreateFolder(
analogItemFolder,
"DataAccess_AnalogType_Array",
"Array");
const string daAnalogArray = "DataAccess_AnalogType_Array_";
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Byte",
"Byte",
BuiltInType.Byte,
ValueRanks.OneDimension,
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }.ToArrayOf());
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Double",
"Double",
BuiltInType.Double,
ValueRanks.OneDimension,
s_doubleArray);
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Duration",
"Duration",
DataTypeIds.Duration,
ValueRanks.OneDimension,
s_doubleArray,
null);
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Float",
"Float",
BuiltInType.Float,
ValueRanks.OneDimension,
s_singleArray);
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Int16",
"Int16",
BuiltInType.Int16,
ValueRanks.OneDimension,
s_shortArray);
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Int32",
"Int32",
BuiltInType.Int32,
ValueRanks.OneDimension,
s_int32Array);
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Int64",
"Int64",
BuiltInType.Int64,
ValueRanks.OneDimension,
new long[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }.ToArrayOf());
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Integer",
"Integer",
BuiltInType.Integer,
ValueRanks.OneDimension,
new long[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }.ToArrayOf());
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "Number",
"Number",
BuiltInType.Number,
ValueRanks.OneDimension,
s_shortArray);
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "SByte",
"SByte",
BuiltInType.SByte,
ValueRanks.OneDimension,
new sbyte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90 }.ToArrayOf());
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "UInt16",
"UInt16",
BuiltInType.UInt16,
ValueRanks.OneDimension,
new ushort[] { 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 }.ToArrayOf());
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "UInt32",
"UInt32",
BuiltInType.UInt32,
ValueRanks.OneDimension,
new uint[] { 30, 31, 32, 33, 34, 35, 36, 37, 38, 39 }.ToArrayOf());
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "UInt64",
"UInt64",
BuiltInType.UInt64,
ValueRanks.OneDimension,
new ulong[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }.ToArrayOf());
CreateAnalogItemVariable(
analogArrayFolder,
daAnalogArray + "UInteger",
"UInteger",
BuiltInType.UInteger,
ValueRanks.OneDimension,
new ulong[] { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }.ToArrayOf());
var doc1 = new XmlDocument();
ResetRandomGenerator(12);
FolderState discreteTypeFolder = CreateFolder(
daFolder,
"DataAccess_DiscreteType",
"DiscreteType");
FolderState twoStateDiscreteFolder = CreateFolder(
discreteTypeFolder,
"DataAccess_TwoStateDiscreteType",
"TwoStateDiscreteType");
const string daTwoStateDiscrete = "DataAccess_TwoStateDiscreteType_";
// Add our Nodes to the folder, and specify their customized discrete enumerations
CreateTwoStateDiscreteItemVariable(
twoStateDiscreteFolder,
daTwoStateDiscrete + "001",
"001",
"red",
"blue");
CreateTwoStateDiscreteItemVariable(
twoStateDiscreteFolder,
daTwoStateDiscrete + "002",
"002",
"open",
"close");
CreateTwoStateDiscreteItemVariable(
twoStateDiscreteFolder,
daTwoStateDiscrete + "003",
"003",
"up",
"down");
CreateTwoStateDiscreteItemVariable(
twoStateDiscreteFolder,
daTwoStateDiscrete + "004",
"004",
"left",
"right");
CreateTwoStateDiscreteItemVariable(
twoStateDiscreteFolder,
daTwoStateDiscrete + "005",
"005",
"circle",
"cross");
FolderState multiStateDiscreteFolder = CreateFolder(
discreteTypeFolder,
"DataAccess_MultiStateDiscreteType",
"MultiStateDiscreteType");
const string daMultiStateDiscrete = "DataAccess_MultiStateDiscreteType_";
// Add our Nodes to the folder, and specify their customized discrete enumerations
CreateMultiStateDiscreteItemVariable(
multiStateDiscreteFolder,
daMultiStateDiscrete + "001",
"001",
"open",
"closed",
"jammed");
CreateMultiStateDiscreteItemVariable(
multiStateDiscreteFolder,
daMultiStateDiscrete + "002",
"002",
"red",
"green",
"blue",
"cyan");
CreateMultiStateDiscreteItemVariable(
multiStateDiscreteFolder,
daMultiStateDiscrete + "003",
"003",
"lolo",
"lo",
"normal",
"hi",
"hihi");
CreateMultiStateDiscreteItemVariable(
multiStateDiscreteFolder,
daMultiStateDiscrete + "004",
"004",
"left",
"right",
"center");
CreateMultiStateDiscreteItemVariable(
multiStateDiscreteFolder,
daMultiStateDiscrete + "005",
"005",
"circle",
"cross",
"triangle");
ResetRandomGenerator(13);
FolderState multiStateValueDiscreteFolder = CreateFolder(
discreteTypeFolder,
"DataAccess_MultiStateValueDiscreteType",
"MultiStateValueDiscreteType");
const string daMultiStateValueDiscrete
= "DataAccess_MultiStateValueDiscreteType_";
// Add our Nodes to the folder, and specify their customized discrete enumerations
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "001",
"001",
s_stringArray1);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "002",
"002",
s_stringArray2);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "003",
"003",
s_stringArray3);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "004",
"004",
s_stringArray4);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "005",
"005",
s_stringArray5);
// Add our Nodes to the folder and specify varying data types
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "Byte",
"Byte",
DataTypeIds.Byte,
s_stringArray1);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "Int16",
"Int16",
DataTypeIds.Int16,
s_stringArray2);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "Int32",
"Int32",
DataTypeIds.Int32,
s_stringArray3);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "Int64",
"Int64",
DataTypeIds.Int64,
s_stringArray4);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "SByte",
"SByte",
DataTypeIds.SByte,
s_stringArray6);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "UInt16",
"UInt16",
DataTypeIds.UInt16,
s_stringArray7);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "UInt32",
"UInt32",
DataTypeIds.UInt32,
s_stringArray8);
CreateMultiStateValueDiscreteItemVariable(
multiStateValueDiscreteFolder,
daMultiStateValueDiscrete + "UInt64",
"UInt64",
DataTypeIds.UInt64,
s_stringArray9);
FolderState arrayItemTypeFolder = CreateFolder(daFolder, "DataAccess_ArrayItemType", "ArrayItemType");
const string daArrayItemType = "DataAccess_ArrayItemType_";
CreateYArrayItemVariable(arrayItemTypeFolder, daArrayItemType + "YArray", "YArray");
CreateXYArrayItemVariable(arrayItemTypeFolder, daArrayItemType + "XYArray", "XYArray");
CreateImageItemVariable(arrayItemTypeFolder, daArrayItemType + "Image", "Image");
CreateCubeItemVariable(arrayItemTypeFolder, daArrayItemType + "Cube", "Cube");
CreateNDimensionArrayItemVariable(arrayItemTypeFolder, daArrayItemType + "NDimension", "NDimension");
ResetRandomGenerator(14);
FolderState referencesFolder = CreateFolder(root, "References", "References");
const string referencesPrefix = "References_";
BaseDataVariableState referencesInstructions = CreateVariable(
referencesFolder,
"References_Instructions",
"Instructions",
DataTypeIds.String,
ValueRanks.Scalar);
referencesInstructions.Value =
"This folder will contain nodes that have specific Reference configurations.";
variables.Add(referencesInstructions);
// create variable nodes with specific references
BaseDataVariableState hasForwardReference = CreateMeshVariable(
referencesFolder,
referencesPrefix + "HasForwardReference",
"HasForwardReference");
hasForwardReference.AddReference(
ReferenceTypeIds.HasCause,
false,
variables[0].NodeId);
variables.Add(hasForwardReference);
BaseDataVariableState hasInverseReference = CreateMeshVariable(
referencesFolder,
referencesPrefix + "HasInverseReference",
"HasInverseReference");
hasInverseReference.AddReference(
ReferenceTypeIds.HasCause,
true,
variables[0].NodeId);
variables.Add(hasInverseReference);
BaseDataVariableState? has3InverseReference = null;
for (int i = 1; i <= 5; i++)
{
string referenceString = "Has3ForwardReferences";
if (i > 1)
{
referenceString += i.ToString(CultureInfo.InvariantCulture);
}
BaseDataVariableState has3ForwardReferences = CreateMeshVariable(
referencesFolder,
referencesPrefix + referenceString,
referenceString);
has3ForwardReferences.AddReference(
ReferenceTypeIds.HasCause,
false,
variables[0].NodeId);
has3ForwardReferences.AddReference(
ReferenceTypeIds.HasCause,
false,
variables[1].NodeId);
has3ForwardReferences.AddReference(
ReferenceTypeIds.HasCause,
false,
variables[2].NodeId);
if (i == 1)
{
has3InverseReference = has3ForwardReferences;
}
variables.Add(has3ForwardReferences);
}
BaseDataVariableState has3InverseReferences = CreateMeshVariable(
referencesFolder,
referencesPrefix + "Has3InverseReferences",
"Has3InverseReferences");
has3InverseReferences.AddReference(
ReferenceTypeIds.HasEffect,
true,
variables[0].NodeId);
has3InverseReferences.AddReference(
ReferenceTypeIds.HasEffect,
true,
variables[1].NodeId);
has3InverseReferences.AddReference(
ReferenceTypeIds.HasEffect,
true,
variables[2].NodeId);
variables.Add(has3InverseReferences);
BaseDataVariableState hasForwardAndInverseReferences = CreateMeshVariable(
referencesFolder,
referencesPrefix + "HasForwardAndInverseReference",
"HasForwardAndInverseReference",
hasForwardReference,
hasInverseReference,
has3InverseReference!,
has3InverseReferences,
variables[0]);
variables.Add(hasForwardAndInverseReferences);
// Node with both a ReferenceType and a SubType of that ReferenceType
// (HasComponent forward + HasOrderedComponent forward, where HasOrderedComponent
// is a subtype of HasComponent — satisfies CTT "HasReferencesOfReferenceTypeAndSubType")
BaseDataVariableState hasReferenceTypeAndSubType = CreateMeshVariable(
referencesFolder,
referencesPrefix + "HasReferenceTypeAndSubType",
"HasReferenceTypeAndSubType");
hasReferenceTypeAndSubType.AddReference(
ReferenceTypeIds.HasComponent,
false,
variables[0].NodeId);
hasReferenceTypeAndSubType.AddReference(
ReferenceTypeIds.HasOrderedComponent,
false,
variables[1].NodeId);
variables.Add(hasReferenceTypeAndSubType);
ResetRandomGenerator(15);
FolderState folderAccessRights = CreateFolder(
root,
"AccessRights",
"AccessRights");
const string accessRights = "AccessRights_";
BaseDataVariableState accessRightsInstructions = CreateVariable(
folderAccessRights,
accessRights + "Instructions",
"Instructions",
DataTypeIds.String,
ValueRanks.Scalar);
accessRightsInstructions.Value =
"This folder will be accessible to all who enter, but contents therein will be secured.";
variables.Add(accessRightsInstructions);
// sub-folder for "AccessAll"
FolderState folderAccessRightsAccessAll = CreateFolder(
folderAccessRights,
"AccessRights_AccessAll",
"AccessAll");
const string accessRightsAccessAll = "AccessRights_AccessAll_";
BaseDataVariableState arAllRO = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "RO",
"RO",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllRO.AccessLevel = AccessLevels.CurrentRead;
arAllRO.UserAccessLevel = AccessLevels.CurrentRead;
variables.Add(arAllRO);
BaseDataVariableState arAllWO = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "WO",
"WO",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllWO.AccessLevel = AccessLevels.CurrentWrite;
arAllWO.UserAccessLevel = AccessLevels.CurrentWrite;
variables.Add(arAllWO);
BaseDataVariableState arAllRW = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "RW",
"RW",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllRW.AccessLevel = AccessLevels.CurrentReadOrWrite;
arAllRW.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variables.Add(arAllRW);
BaseDataVariableState arAllRONotUser = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "RO_NotUser",
"RO_NotUser",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllRONotUser.AccessLevel = AccessLevels.CurrentRead;
arAllRONotUser.UserAccessLevel = AccessLevels.None;
variables.Add(arAllRONotUser);
BaseDataVariableState arAllWONotUser = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "WO_NotUser",
"WO_NotUser",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllWONotUser.AccessLevel = AccessLevels.CurrentWrite;
arAllWONotUser.UserAccessLevel = AccessLevels.None;
variables.Add(arAllWONotUser);
BaseDataVariableState arAllRWNotUser = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "RW_NotUser",
"RW_NotUser",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllRWNotUser.AccessLevel = AccessLevels.CurrentReadOrWrite;
arAllRWNotUser.UserAccessLevel = AccessLevels.CurrentRead;
variables.Add(arAllRWNotUser);
BaseDataVariableState arAllROUserRW = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "RO_User1_RW",
"RO_User1_RW",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllROUserRW.AccessLevel = AccessLevels.CurrentRead;
arAllROUserRW.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variables.Add(arAllROUserRW);
BaseDataVariableState arAllROGroupRW = CreateVariable(
folderAccessRightsAccessAll,
accessRightsAccessAll + "RO_Group1_RW",
"RO_Group1_RW",
BuiltInType.Int16,
ValueRanks.Scalar);
arAllROGroupRW.AccessLevel = AccessLevels.CurrentRead;
arAllROGroupRW.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variables.Add(arAllROGroupRW);
// sub-folder for "AccessUser1"
FolderState folderAccessRightsAccessUser1 = CreateFolder(
folderAccessRights,
"AccessRights_AccessUser1",
"AccessUser1");
const string accessRightsAccessUser1 = "AccessRights_AccessUser1_";
BaseDataVariableState arUserRO = CreateVariable(
folderAccessRightsAccessUser1,
accessRightsAccessUser1 + "RO",
"RO",
BuiltInType.Int16,
ValueRanks.Scalar);
arUserRO.AccessLevel = AccessLevels.CurrentRead;
arUserRO.UserAccessLevel = AccessLevels.CurrentRead;
variables.Add(arUserRO);
BaseDataVariableState arUserWO = CreateVariable(
folderAccessRightsAccessUser1,
accessRightsAccessUser1 + "WO",
"WO",
BuiltInType.Int16,
ValueRanks.Scalar);
arUserWO.AccessLevel = AccessLevels.CurrentWrite;
arUserWO.UserAccessLevel = AccessLevels.CurrentWrite;
variables.Add(arUserWO);
BaseDataVariableState arUserRW = CreateVariable(
folderAccessRightsAccessUser1,
accessRightsAccessUser1 + "RW",
"RW",
BuiltInType.Int16,
ValueRanks.Scalar);
arUserRW.AccessLevel = AccessLevels.CurrentReadOrWrite;
arUserRW.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variables.Add(arUserRW);
// sub-folder for "AccessGroup1"
FolderState folderAccessRightsAccessGroup1 = CreateFolder(
folderAccessRights,
"AccessRights_AccessGroup1",
"AccessGroup1");
const string accessRightsAccessGroup1 = "AccessRights_AccessGroup1_";
BaseDataVariableState arGroupRO = CreateVariable(
folderAccessRightsAccessGroup1,
accessRightsAccessGroup1 + "RO",
"RO",
BuiltInType.Int16,
ValueRanks.Scalar);
arGroupRO.AccessLevel = AccessLevels.CurrentRead;
arGroupRO.UserAccessLevel = AccessLevels.CurrentRead;
variables.Add(arGroupRO);
BaseDataVariableState arGroupWO = CreateVariable(
folderAccessRightsAccessGroup1,
accessRightsAccessGroup1 + "WO",
"WO",
BuiltInType.Int16,
ValueRanks.Scalar);
arGroupWO.AccessLevel = AccessLevels.CurrentWrite;
arGroupWO.UserAccessLevel = AccessLevels.CurrentWrite;
variables.Add(arGroupWO);
BaseDataVariableState arGroupRW = CreateVariable(
folderAccessRightsAccessGroup1,
accessRightsAccessGroup1 + "RW",
"RW",
BuiltInType.Int16,
ValueRanks.Scalar);
arGroupRW.AccessLevel = AccessLevels.CurrentReadOrWrite;
arGroupRW.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variables.Add(arGroupRW);
// sub folder for "RolePermissions"
FolderState folderRolePermissions = CreateFolder(
folderAccessRights,
"AccessRights_RolePermissions",
"RolePermissions");
const string rolePermissions = "AccessRights_RolePermissions_";
BaseDataVariableState rpAnonymous = CreateVariable(
folderRolePermissions,
rolePermissions + "AnonymousAccess",
"AnonymousAccess",
BuiltInType.Int16,
ValueRanks.Scalar);
rpAnonymous.Description = LocalizedText.From(
"This node can be accessed by users that have Anonymous Role");
rpAnonymous.RolePermissions =
[
// allow access to users with Anonymous role
new RolePermissionType
{
RoleId = ObjectIds.WellKnownRole_Anonymous,
Permissions = (uint)(
PermissionType.Browse |
PermissionType.Read |
PermissionType.ReadRolePermissions |
PermissionType.Write)
}
];
variables.Add(rpAnonymous);
BaseDataVariableState rpAuthenticatedUser = CreateVariable(
folderRolePermissions,
rolePermissions + "AuthenticatedUser",
"AuthenticatedUser",
BuiltInType.Int16,
ValueRanks.Scalar);
rpAuthenticatedUser.Description =
LocalizedText.From("This node can be accessed by users that have AuthenticatedUser Role");
rpAuthenticatedUser.RolePermissions =
[
// allow access to users with AuthenticatedUser role
new RolePermissionType
{
RoleId = ObjectIds.WellKnownRole_AuthenticatedUser,
Permissions = (uint)(
PermissionType.Browse |
PermissionType.Read |
PermissionType.ReadRolePermissions |
PermissionType.Write)
}
];
variables.Add(rpAuthenticatedUser);
BaseDataVariableState rpSecurityAdminUser = CreateVariable(
folderRolePermissions,
rolePermissions + "SecurityAdmin",
"SecurityAdmin",
BuiltInType.Int16,
ValueRanks.Scalar);
rpSecurityAdminUser.Description = LocalizedText.From(
"This node can be accessed by users that have SecurityAdmin Role over an encrypted connection");
rpSecurityAdminUser.AccessRestrictions
= AccessRestrictionType.EncryptionRequired;
rpSecurityAdminUser.RolePermissions =
[
// allow access to users with SecurityAdmin role
new RolePermissionType
{
RoleId = ObjectIds.WellKnownRole_SecurityAdmin,
Permissions = (uint)(
PermissionType.Browse |
PermissionType.Read |
PermissionType.ReadRolePermissions |
PermissionType.Write)
}
];
variables.Add(rpSecurityAdminUser);
BaseDataVariableState rpConfigAdminUser = CreateVariable(
folderRolePermissions,
rolePermissions + "ConfigureAdmin",
"ConfigureAdmin",
BuiltInType.Int16,
ValueRanks.Scalar);
rpConfigAdminUser.Description = LocalizedText.From(
"This node can be accessed by users that have ConfigureAdmin Role over an encrypted connection");
rpConfigAdminUser.AccessRestrictions = AccessRestrictionType.EncryptionRequired;
rpConfigAdminUser.RolePermissions =
[
// allow access to users with ConfigureAdmin role
new RolePermissionType
{
RoleId = ObjectIds.WellKnownRole_ConfigureAdmin,
Permissions = (uint)(
PermissionType.Browse |
PermissionType.Read |
PermissionType.ReadRolePermissions |
PermissionType.Write)
}
];
variables.Add(rpConfigAdminUser);
// sub-folder for "AccessRestrictions"
FolderState folderAccessRestrictions = CreateFolder(
folderAccessRights,
"AccessRights_AccessRestrictions",
"AccessRestrictions");
const string accessRestrictions = "AccessRights_AccessRestrictions_";
BaseDataVariableState arNone = CreateVariable(
folderAccessRestrictions,
accessRestrictions + "None",
"None",
BuiltInType.Int16,
ValueRanks.Scalar);
arNone.AccessLevel = AccessLevels.CurrentRead;
arNone.UserAccessLevel = AccessLevels.CurrentRead;
arNone.AccessRestrictions = AccessRestrictionType.None;
variables.Add(arNone);
BaseDataVariableState arSigningRequired = CreateVariable(
folderAccessRestrictions,
accessRestrictions + "SigningRequired",
"SigningRequired",
BuiltInType.Int16,
ValueRanks.Scalar);
arSigningRequired.AccessLevel = AccessLevels.CurrentRead;
arSigningRequired.UserAccessLevel = AccessLevels.CurrentRead;
arSigningRequired.AccessRestrictions = AccessRestrictionType.SigningRequired;
variables.Add(arSigningRequired);
BaseDataVariableState arEncryptionRequired = CreateVariable(
folderAccessRestrictions,
accessRestrictions + "EncryptionRequired",
"EncryptionRequired",
BuiltInType.Int16,
ValueRanks.Scalar);
arEncryptionRequired.AccessLevel = AccessLevels.CurrentRead;
arEncryptionRequired.UserAccessLevel = AccessLevels.CurrentRead;
arEncryptionRequired.AccessRestrictions
= AccessRestrictionType.EncryptionRequired;
variables.Add(arEncryptionRequired);
BaseDataVariableState arSessionRequired = CreateVariable(
folderAccessRestrictions,
accessRestrictions + "SessionRequired",
"SessionRequired",
BuiltInType.Int16,
ValueRanks.Scalar);
arSessionRequired.AccessLevel = AccessLevels.CurrentRead;
arSessionRequired.UserAccessLevel = AccessLevels.CurrentRead;
arSessionRequired.AccessRestrictions = AccessRestrictionType.SessionRequired;
variables.Add(arSessionRequired);
ResetRandomGenerator(16);
FolderState nodeIdsFolder = CreateFolder(root, "NodeIds", "NodeIds");
const string nodeIds = "NodeIds_";
BaseDataVariableState nodeIdsInstructions = CreateVariable(
nodeIdsFolder,
nodeIds + "Instructions",
"Instructions",
DataTypeIds.String,
ValueRanks.Scalar);
nodeIdsInstructions.Value =
"All supported Node types are available except whichever is in use for the other nodes.";
variables.Add(nodeIdsInstructions);
BaseDataVariableState integerNodeId = CreateVariable(
nodeIdsFolder,
nodeIds + "Int16Integer",
"Int16Integer",
DataTypeIds.Int16,
ValueRanks.Scalar);
integerNodeId.NodeId = new NodeId(9202, NamespaceIndex);
variables.Add(integerNodeId);
variables.Add(
CreateVariable(
nodeIdsFolder,
nodeIds + "Int16String",
"Int16String",
DataTypeIds.Int16,
ValueRanks.Scalar));
BaseDataVariableState guidNodeId = CreateVariable(
nodeIdsFolder,
nodeIds + "Int16GUID",
"Int16GUID",
DataTypeIds.Int16,
ValueRanks.Scalar);
guidNodeId.NodeId = new NodeId(
new Guid("00000000-0000-0000-0000-000000009204"),
NamespaceIndex);
variables.Add(guidNodeId);
BaseDataVariableState opaqueNodeId = CreateVariable(
nodeIdsFolder,
nodeIds + "Int16Opaque",
"Int16Opaque",
DataTypeIds.Int16,
ValueRanks.Scalar);
opaqueNodeId.NodeId = new NodeId(ByteString.From([9, 2, 0, 5]), NamespaceIndex);
variables.Add(opaqueNodeId);
FolderState eventsFolder = CreateFolder(nodeIdsFolder, "NodeIds_Events", "Events");
const string nodeIdsEvents = "NodeIds_Events_";
BaseDataVariableState triggerNode01 = CreateVariable(
eventsFolder,
nodeIdsEvents + "TriggerNode01",
"TriggerNode01",
DataTypeIds.Int32,
ValueRanks.Scalar);
triggerNode01.OnSimpleWriteValue = OnWriteTriggerNode;
variables.Add(triggerNode01);
BaseDataVariableState triggerNode02 = CreateVariable(
eventsFolder,
nodeIdsEvents + "TriggerNode02",
"TriggerNode02",
DataTypeIds.Int32,
ValueRanks.Scalar);
triggerNode02.OnSimpleWriteValue = OnWriteTriggerNode;
variables.Add(triggerNode02);
ResetRandomGenerator(17);
FolderState methodsFolder = CreateFolder(root, "Methods", "Methods");
const string methods = "Methods_";
BaseDataVariableState methodsInstructions = CreateVariable(
methodsFolder,
methods + "Instructions",
"Instructions",
DataTypeIds.String,
ValueRanks.Scalar);
methodsInstructions.Value
= "Contains methods with varying parameter definitions.";
variables.Add(methodsInstructions);
MethodState voidMethod = CreateMethod(methodsFolder, methods + "Void", "Void");
voidMethod.OnCallMethod = new GenericMethodCalledEventHandler(OnVoidCall);
MethodState addMethod = CreateMethod(methodsFolder, methods + "Add", "Add");
// set input arguments
addMethod.InputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(addMethod)
{
NodeId = new NodeId(addMethod.BrowseName.Name + "InArgs", NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.InputArguments)
};
addMethod.InputArguments.DisplayName = LocalizedText.From(addMethod.InputArguments.BrowseName.Name!);
addMethod.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
addMethod.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
addMethod.InputArguments.DataType = DataTypeIds.Argument;
addMethod.InputArguments.ValueRank = ValueRanks.OneDimension;
addMethod.InputArguments.Value =
[
new Argument
{
Name = "Float value",
Description = LocalizedText.From("Float value"),
DataType = DataTypeIds.Float,
ValueRank = ValueRanks.Scalar
},
new Argument
{
Name = "UInt32 value",
Description = LocalizedText.From("UInt32 value"),
DataType = DataTypeIds.UInt32,
ValueRank = ValueRanks.Scalar
}
];
// set output arguments
addMethod.OutputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(addMethod)
{
NodeId = new NodeId(addMethod.BrowseName.Name + "OutArgs", NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.OutputArguments)
};
addMethod.OutputArguments.DisplayName = LocalizedText.From(addMethod.OutputArguments.BrowseName.Name!);
addMethod.OutputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
addMethod.OutputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
addMethod.OutputArguments.DataType = DataTypeIds.Argument;
addMethod.OutputArguments.ValueRank = ValueRanks.OneDimension;
addMethod.OutputArguments.Value =
[
new Argument
{
Name = "Add Result",
Description = LocalizedText.From("Add Result"),
DataType = DataTypeIds.Float,
ValueRank = ValueRanks.Scalar
}
];
addMethod.OnCallMethod = new GenericMethodCalledEventHandler(OnAddCall);
MethodState multiplyMethod = CreateMethod(
methodsFolder,
methods + "Multiply",
"Multiply");
// set input arguments
multiplyMethod.InputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(multiplyMethod)
{
NodeId = new NodeId(
multiplyMethod.BrowseName.Name + "InArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.InputArguments)
};
multiplyMethod.InputArguments.DisplayName = LocalizedText.From(
multiplyMethod.InputArguments.BrowseName.Name!);
multiplyMethod.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
multiplyMethod.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
multiplyMethod.InputArguments.DataType = DataTypeIds.Argument;
multiplyMethod.InputArguments.ValueRank = ValueRanks.OneDimension;
multiplyMethod.InputArguments.Value =
[
new Argument
{
Name = "Int16 value",
Description = LocalizedText.From("Int16 value"),
DataType = DataTypeIds.Int16,
ValueRank = ValueRanks.Scalar
},
new Argument
{
Name = "UInt16 value",
Description = LocalizedText.From("UInt16 value"),
DataType = DataTypeIds.UInt16,
ValueRank = ValueRanks.Scalar
}
];
// set output arguments
multiplyMethod.OutputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(multiplyMethod)
{
NodeId = new NodeId(
multiplyMethod.BrowseName.Name + "OutArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.OutputArguments)
};
multiplyMethod.OutputArguments.DisplayName = LocalizedText.From(
multiplyMethod.OutputArguments.BrowseName.Name!);
multiplyMethod.OutputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
multiplyMethod.OutputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
multiplyMethod.OutputArguments.DataType = DataTypeIds.Argument;
multiplyMethod.OutputArguments.ValueRank = ValueRanks.OneDimension;
multiplyMethod.OutputArguments.Value =
[
new Argument
{
Name = "Multiply Result",
Description = LocalizedText.From("Multiply Result"),
DataType = DataTypeIds.Int32,
ValueRank = ValueRanks.Scalar
}
];
multiplyMethod.OnCallMethod
= new GenericMethodCalledEventHandler(OnMultiplyCall);
MethodState divideMethod = CreateMethod(
methodsFolder,
methods + "Divide",
"Divide");
// set input arguments
divideMethod.InputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(divideMethod)
{
NodeId = new NodeId(
divideMethod.BrowseName.Name + "InArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.InputArguments)
};
divideMethod.InputArguments.DisplayName = LocalizedText.From(
divideMethod.InputArguments.BrowseName.Name!);
divideMethod.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
divideMethod.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
divideMethod.InputArguments.DataType = DataTypeIds.Argument;
divideMethod.InputArguments.ValueRank = ValueRanks.OneDimension;
divideMethod.InputArguments.Value =
[
new Argument
{
Name = "Int32 value",
Description = LocalizedText.From("Int32 value"),
DataType = DataTypeIds.Int32,
ValueRank = ValueRanks.Scalar
},
new Argument
{
Name = "UInt16 value",
Description = LocalizedText.From("UInt16 value"),
DataType = DataTypeIds.UInt16,
ValueRank = ValueRanks.Scalar
}
];
// set output arguments
divideMethod.OutputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(divideMethod)
{
NodeId = new NodeId(
divideMethod.BrowseName.Name + "OutArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.OutputArguments)
};
divideMethod.OutputArguments.DisplayName = LocalizedText.From(
divideMethod.OutputArguments.BrowseName.Name!);
divideMethod.OutputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
divideMethod.OutputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
divideMethod.OutputArguments.DataType = DataTypeIds.Argument;
divideMethod.OutputArguments.ValueRank = ValueRanks.OneDimension;
divideMethod.OutputArguments.Value =
[
new Argument
{
Name = "Divide Result",
Description = LocalizedText.From("Divide Result"),
DataType = DataTypeIds.Float,
ValueRank = ValueRanks.Scalar
}
];
divideMethod.OnCallMethod = new GenericMethodCalledEventHandler(OnDivideCall);
MethodState substractMethod = CreateMethod(
methodsFolder,
methods + "Substract",
"Substract");
// set input arguments
substractMethod.InputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(substractMethod)
{
NodeId = new NodeId(
substractMethod.BrowseName.Name + "InArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.InputArguments)
};
substractMethod.InputArguments.DisplayName = LocalizedText.From(
substractMethod.InputArguments.BrowseName.Name!);
substractMethod.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
substractMethod.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
substractMethod.InputArguments.DataType = DataTypeIds.Argument;
substractMethod.InputArguments.ValueRank = ValueRanks.OneDimension;
substractMethod.InputArguments.Value =
[
new Argument
{
Name = "Int16 value",
Description = LocalizedText.From("Int16 value"),
DataType = DataTypeIds.Int16,
ValueRank = ValueRanks.Scalar
},
new Argument
{
Name = "Byte value",
Description = LocalizedText.From("Byte value"),
DataType = DataTypeIds.Byte,
ValueRank = ValueRanks.Scalar
}
];
// set output arguments
substractMethod.OutputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(substractMethod)
{
NodeId = new NodeId(
substractMethod.BrowseName.Name + "OutArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.OutputArguments)
};
substractMethod.OutputArguments.DisplayName = LocalizedText.From(
substractMethod.OutputArguments.BrowseName.Name!);
substractMethod.OutputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
substractMethod.OutputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
substractMethod.OutputArguments.DataType = DataTypeIds.Argument;
substractMethod.OutputArguments.ValueRank = ValueRanks.OneDimension;
substractMethod.OutputArguments.Value =
[
new Argument
{
Name = "Substract Result",
Description = LocalizedText.From("Substract Result"),
DataType = DataTypeIds.Int16,
ValueRank = ValueRanks.Scalar
}
];
substractMethod.OnCallMethod
= new GenericMethodCalledEventHandler(OnSubstractCall);
MethodState helloMethod = CreateMethod(
methodsFolder,
methods + "Hello",
"Hello");
// set input arguments
helloMethod.InputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(helloMethod)
{
NodeId = new NodeId(helloMethod.BrowseName.Name + "InArgs", NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.InputArguments)
};
helloMethod.InputArguments.DisplayName = LocalizedText.From(
helloMethod.InputArguments.BrowseName.Name!);
helloMethod.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
helloMethod.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
helloMethod.InputArguments.DataType = DataTypeIds.Argument;
helloMethod.InputArguments.ValueRank = ValueRanks.OneDimension;
helloMethod.InputArguments.Value =
[
new Argument
{
Name = "String value",
Description = LocalizedText.From("String value"),
DataType = DataTypeIds.String,
ValueRank = ValueRanks.Scalar
}
];
// set output arguments
helloMethod.OutputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(helloMethod)
{
NodeId = new NodeId(
helloMethod.BrowseName.Name + "OutArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.OutputArguments)
};
helloMethod.OutputArguments.DisplayName = LocalizedText.From(
helloMethod.OutputArguments.BrowseName.Name!);
helloMethod.OutputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
helloMethod.OutputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
helloMethod.OutputArguments.DataType = DataTypeIds.Argument;
helloMethod.OutputArguments.ValueRank = ValueRanks.OneDimension;
helloMethod.OutputArguments.Value =
[
new Argument
{
Name = "Hello Result",
Description = LocalizedText.From("Hello Result"),
DataType = DataTypeIds.String,
ValueRank = ValueRanks.Scalar
}
];
helloMethod.OnCallMethod = new GenericMethodCalledEventHandler(OnHelloCall);
MethodState inputMethod = CreateMethod(
methodsFolder,
methods + "Input",
"Input");
// set input arguments
inputMethod.InputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(inputMethod)
{
NodeId = new NodeId(inputMethod.BrowseName.Name + "InArgs", NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.InputArguments)
};
inputMethod.InputArguments.DisplayName = LocalizedText.From(
inputMethod.InputArguments.BrowseName.Name!);
inputMethod.InputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
inputMethod.InputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
inputMethod.InputArguments.DataType = DataTypeIds.Argument;
inputMethod.InputArguments.ValueRank = ValueRanks.OneDimension;
inputMethod.InputArguments.Value =
[
new Argument
{
Name = "String value",
Description = LocalizedText.From("String value"),
DataType = DataTypeIds.String,
ValueRank = ValueRanks.Scalar
}
];
inputMethod.OnCallMethod = new GenericMethodCalledEventHandler(OnInputCall);
MethodState outputMethod = CreateMethod(
methodsFolder,
methods + "Output",
"Output");
// set output arguments
outputMethod.OutputArguments = new PropertyState<ArrayOf<Argument>>.Implementation<StructureBuilder<Argument>>(helloMethod)
{
NodeId = new NodeId(
helloMethod.BrowseName.Name + "OutArgs",
NamespaceIndex),
BrowseName = QualifiedName.From(BrowseNames.OutputArguments)
};
outputMethod.OutputArguments.DisplayName = LocalizedText.From(
outputMethod.OutputArguments.BrowseName.Name!);
outputMethod.OutputArguments.TypeDefinitionId = VariableTypeIds.PropertyType;
outputMethod.OutputArguments.ReferenceTypeId = ReferenceTypeIds.HasProperty;
outputMethod.OutputArguments.DataType = DataTypeIds.Argument;
outputMethod.OutputArguments.ValueRank = ValueRanks.OneDimension;
outputMethod.OutputArguments.Value =
[
new Argument
{
Name = "Output Result",
Description = LocalizedText.From("Output Result"),
DataType = DataTypeIds.String,
ValueRank = ValueRanks.Scalar
}
];
outputMethod.OnCallMethod = new GenericMethodCalledEventHandler(OnOutputCall);
ResetRandomGenerator(18);
FolderState viewsFolder = CreateFolder(root, "Views", "Views");
const string views = "Views_";
ViewState viewStateOperations = await CreateViewAsync(
viewsFolder,
externalReferences,
views + "Operations",
"Operations",
cancellationToken).ConfigureAwait(false);
viewStateOperations.AddReference(
ReferenceTypeIds.Organizes,
false,
massFolder.NodeId);
massFolder.AddReference(
ReferenceTypeIds.Organizes,
true,
viewStateOperations.NodeId);
ViewState viewStateEngineering = await CreateViewAsync(
viewsFolder,
externalReferences,
views + "Engineering",
"Engineering",
cancellationToken).ConfigureAwait(false);
viewStateEngineering.AddReference(
ReferenceTypeIds.Organizes,
false,
simulationFolder.NodeId);
simulationFolder.AddReference(
ReferenceTypeIds.Organizes,
true,
viewStateEngineering.NodeId);
ResetRandomGenerator(19);
FolderState localesFolder = CreateFolder(root, "Locales", "Locales");
const string locales = "Locales_";
BaseDataVariableState qnEnglishVariable = CreateVariable(
localesFolder,
locales + "QNEnglish",
"QNEnglish",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnEnglishVariable.Description = new LocalizedText("en", "English");
qnEnglishVariable.Value = new QualifiedName("Hello World", NamespaceIndex);
variables.Add(qnEnglishVariable);
BaseDataVariableState ltEnglishVariable = CreateVariable(
localesFolder,
locales + "LTEnglish",
"LTEnglish",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltEnglishVariable.Description = new LocalizedText("en", "English");
ltEnglishVariable.Value = new LocalizedText("en", "Hello World");
variables.Add(ltEnglishVariable);
BaseDataVariableState qnFrancaisVariable = CreateVariable(
localesFolder,
locales + "QNFrancais",
"QNFrancais",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnFrancaisVariable.Description = new LocalizedText("en", "Francais");
qnFrancaisVariable.Value
= new QualifiedName("Salut tout le monde", NamespaceIndex);
variables.Add(qnFrancaisVariable);
BaseDataVariableState ltFrancaisVariable = CreateVariable(
localesFolder,
locales + "LTFrancais",
"LTFrancais",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltFrancaisVariable.Description = new LocalizedText("en", "Francais");
ltFrancaisVariable.Value = new LocalizedText("fr", "Salut tout le monde");
variables.Add(ltFrancaisVariable);
BaseDataVariableState qnDeutschVariable = CreateVariable(
localesFolder,
locales + "QNDeutsch",
"QNDeutsch",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnDeutschVariable.Description = new LocalizedText("en", "Deutsch");
qnDeutschVariable.Value = new QualifiedName("Hallo Welt", NamespaceIndex);
variables.Add(qnDeutschVariable);
BaseDataVariableState ltDeutschVariable = CreateVariable(
localesFolder,
locales + "LTDeutsch",
"LTDeutsch",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltDeutschVariable.Description = new LocalizedText("en", "Deutsch");
ltDeutschVariable.Value = new LocalizedText("de", "Hallo Welt");
variables.Add(ltDeutschVariable);
BaseDataVariableState qnEspanolVariable = CreateVariable(
localesFolder,
locales + "QNEspanol",
"QNEspanol",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnEspanolVariable.Description = new LocalizedText("en", "Espanol");
qnEspanolVariable.Value = new QualifiedName("Hola mundo", NamespaceIndex);
variables.Add(qnEspanolVariable);
BaseDataVariableState ltEspanolVariable = CreateVariable(
localesFolder,
locales + "LTEspanol",
"LTEspanol",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltEspanolVariable.Description = new LocalizedText("en", "Espanol");
ltEspanolVariable.Value = new LocalizedText("es", "Hola mundo");
variables.Add(ltEspanolVariable);
BaseDataVariableState qnJapaneseVariable = CreateVariable(
localesFolder,
locales + "QN日本の",
"QN日本の",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnJapaneseVariable.Description = new LocalizedText("en", "Japanese");
qnJapaneseVariable.Value = new QualifiedName("ハローワールド", NamespaceIndex);
variables.Add(qnJapaneseVariable);
BaseDataVariableState ltJapaneseVariable = CreateVariable(
localesFolder,
locales + "LT日本の",
"LT日本の",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltJapaneseVariable.Description = new LocalizedText("en", "Japanese");
ltJapaneseVariable.Value = new LocalizedText("jp", "ハローワールド");
variables.Add(ltJapaneseVariable);
BaseDataVariableState qnChineseVariable = CreateVariable(
localesFolder,
locales + "QN中國的",
"QN中國的",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnChineseVariable.Description = new LocalizedText("en", "Chinese");
qnChineseVariable.Value = new QualifiedName("世界您好", NamespaceIndex);
variables.Add(qnChineseVariable);
BaseDataVariableState ltChineseVariable = CreateVariable(
localesFolder,
locales + "LT中國的",
"LT中國的",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltChineseVariable.Description = new LocalizedText("en", "Chinese");
ltChineseVariable.Value = new LocalizedText("ch", "世界您好");
variables.Add(ltChineseVariable);
BaseDataVariableState qnRussianVariable = CreateVariable(
localesFolder,
locales + "QNрусский",
"QNрусский",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnRussianVariable.Description = new LocalizedText("en", "Russian");
qnRussianVariable.Value = new QualifiedName("LTрусский", NamespaceIndex);
variables.Add(qnRussianVariable);
BaseDataVariableState ltRussianVariable = CreateVariable(
localesFolder,
locales + "LTрусский",
"LTрусский",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltRussianVariable.Description = new LocalizedText("en", "Russian");
ltRussianVariable.Value = new LocalizedText("ru", "LTрусский");
variables.Add(ltRussianVariable);
BaseDataVariableState qnArabicVariable = CreateVariable(
localesFolder,
locales + "QNالعربية",
"QNالعربية",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnArabicVariable.Description = new LocalizedText("en", "Arabic");
qnArabicVariable.Value = new QualifiedName("مرحبا بالعال", NamespaceIndex);
variables.Add(qnArabicVariable);
BaseDataVariableState ltArabicVariable = CreateVariable(
localesFolder,
locales + "LTالعربية",
"LTالعربية",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltArabicVariable.Description = new LocalizedText("en", "Arabic");
ltArabicVariable.Value = new LocalizedText("ae", "مرحبا بالعال");
variables.Add(ltArabicVariable);
BaseDataVariableState qnKlingonVariable = CreateVariable(
localesFolder,
locales + "QNtlhIngan",
"QNtlhIngan",
DataTypeIds.QualifiedName,
ValueRanks.Scalar);
qnKlingonVariable.Description = new LocalizedText("en", "Klingon");
qnKlingonVariable.Value = new QualifiedName("qo' vIvan", NamespaceIndex);
variables.Add(qnKlingonVariable);
BaseDataVariableState ltKlingonVariable = CreateVariable(
localesFolder,
locales + "LTtlhIngan",
"LTtlhIngan",
DataTypeIds.LocalizedText,
ValueRanks.Scalar);
ltKlingonVariable.Description = new LocalizedText("en", "Klingon");
ltKlingonVariable.Value = new LocalizedText("ko", "qo' vIvan");
variables.Add(ltKlingonVariable);
ResetRandomGenerator(20);
FolderState folderAttributes = CreateFolder(root, "Attributes", "Attributes");
FolderState folderAttributesAccessAll = CreateFolder(
folderAttributes,
"Attributes_AccessAll",
"AccessAll");
const string attributesAccessAll = "Attributes_AccessAll_";
BaseDataVariableState accessLevelAccessAll = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "AccessLevel",
"AccessLevel",
DataTypeIds.Double,
ValueRanks.Scalar);
accessLevelAccessAll.WriteMask = AttributeWriteMask.AccessLevel;
accessLevelAccessAll.UserWriteMask = AttributeWriteMask.AccessLevel;
variables.Add(accessLevelAccessAll);
BaseDataVariableState arrayDimensionsAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "ArrayDimensions",
"ArrayDimensions",
DataTypeIds.Double,
ValueRanks.Scalar);
arrayDimensionsAccessLevel.WriteMask = AttributeWriteMask.ArrayDimensions;
arrayDimensionsAccessLevel.UserWriteMask = AttributeWriteMask.ArrayDimensions;
variables.Add(arrayDimensionsAccessLevel);
BaseDataVariableState browseNameAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "BrowseName",
"BrowseName",
DataTypeIds.Double,
ValueRanks.Scalar);
browseNameAccessLevel.WriteMask = AttributeWriteMask.BrowseName;
browseNameAccessLevel.UserWriteMask = AttributeWriteMask.BrowseName;
variables.Add(browseNameAccessLevel);
BaseDataVariableState containsNoLoopsAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "ContainsNoLoops",
"ContainsNoLoops",
DataTypeIds.Double,
ValueRanks.Scalar);
containsNoLoopsAccessLevel.WriteMask = AttributeWriteMask.ContainsNoLoops;
containsNoLoopsAccessLevel.UserWriteMask = AttributeWriteMask.ContainsNoLoops;
variables.Add(containsNoLoopsAccessLevel);
BaseDataVariableState dataTypeAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "DataType",
"DataType",
DataTypeIds.Double,
ValueRanks.Scalar);
dataTypeAccessLevel.WriteMask = AttributeWriteMask.DataType;
dataTypeAccessLevel.UserWriteMask = AttributeWriteMask.DataType;
variables.Add(dataTypeAccessLevel);
BaseDataVariableState descriptionAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "Description",
"Description",
DataTypeIds.Double,
ValueRanks.Scalar);
descriptionAccessLevel.WriteMask = AttributeWriteMask.Description;
descriptionAccessLevel.UserWriteMask = AttributeWriteMask.Description;
variables.Add(descriptionAccessLevel);
BaseDataVariableState eventNotifierAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "EventNotifier",
"EventNotifier",
DataTypeIds.Double,
ValueRanks.Scalar);
eventNotifierAccessLevel.WriteMask = AttributeWriteMask.EventNotifier;
eventNotifierAccessLevel.UserWriteMask = AttributeWriteMask.EventNotifier;
variables.Add(eventNotifierAccessLevel);
BaseDataVariableState executableAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "Executable",
"Executable",
DataTypeIds.Double,
ValueRanks.Scalar);
executableAccessLevel.WriteMask = AttributeWriteMask.Executable;
executableAccessLevel.UserWriteMask = AttributeWriteMask.Executable;
variables.Add(executableAccessLevel);
BaseDataVariableState historizingAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "Historizing",
"Historizing",
DataTypeIds.Double,
ValueRanks.Scalar);
historizingAccessLevel.WriteMask = AttributeWriteMask.Historizing;
historizingAccessLevel.UserWriteMask = AttributeWriteMask.Historizing;
variables.Add(historizingAccessLevel);
BaseDataVariableState inverseNameAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "InverseName",
"InverseName",
DataTypeIds.Double,
ValueRanks.Scalar);
inverseNameAccessLevel.WriteMask = AttributeWriteMask.InverseName;
inverseNameAccessLevel.UserWriteMask = AttributeWriteMask.InverseName;
variables.Add(inverseNameAccessLevel);
BaseDataVariableState isAbstractAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "IsAbstract",
"IsAbstract",
DataTypeIds.Double,
ValueRanks.Scalar);
isAbstractAccessLevel.WriteMask = AttributeWriteMask.IsAbstract;
isAbstractAccessLevel.UserWriteMask = AttributeWriteMask.IsAbstract;
variables.Add(isAbstractAccessLevel);
BaseDataVariableState minimumSamplingIntervalAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "MinimumSamplingInterval",
"MinimumSamplingInterval",
DataTypeIds.Double,
ValueRanks.Scalar);
minimumSamplingIntervalAccessLevel.WriteMask
= AttributeWriteMask.MinimumSamplingInterval;
minimumSamplingIntervalAccessLevel.UserWriteMask
= AttributeWriteMask.MinimumSamplingInterval;
variables.Add(minimumSamplingIntervalAccessLevel);
BaseDataVariableState nodeClassIntervalAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "NodeClass",
"NodeClass",
DataTypeIds.Double,
ValueRanks.Scalar);
nodeClassIntervalAccessLevel.WriteMask = AttributeWriteMask.NodeClass;
nodeClassIntervalAccessLevel.UserWriteMask = AttributeWriteMask.NodeClass;
variables.Add(nodeClassIntervalAccessLevel);
BaseDataVariableState nodeIdAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "NodeId",
"NodeId",
DataTypeIds.Double,
ValueRanks.Scalar);
nodeIdAccessLevel.WriteMask = AttributeWriteMask.NodeId;
nodeIdAccessLevel.UserWriteMask = AttributeWriteMask.NodeId;
variables.Add(nodeIdAccessLevel);
BaseDataVariableState symmetricAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "Symmetric",
"Symmetric",
DataTypeIds.Double,
ValueRanks.Scalar);
symmetricAccessLevel.WriteMask = AttributeWriteMask.Symmetric;
symmetricAccessLevel.UserWriteMask = AttributeWriteMask.Symmetric;
variables.Add(symmetricAccessLevel);
BaseDataVariableState userAccessLevelAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "UserAccessLevel",
"UserAccessLevel",
DataTypeIds.Double,
ValueRanks.Scalar);
userAccessLevelAccessLevel.WriteMask = AttributeWriteMask.UserAccessLevel;
userAccessLevelAccessLevel.UserWriteMask = AttributeWriteMask.UserAccessLevel;
variables.Add(userAccessLevelAccessLevel);
BaseDataVariableState userExecutableAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "UserExecutable",
"UserExecutable",
DataTypeIds.Double,
ValueRanks.Scalar);
userExecutableAccessLevel.WriteMask = AttributeWriteMask.UserExecutable;
userExecutableAccessLevel.UserWriteMask = AttributeWriteMask.UserExecutable;
variables.Add(userExecutableAccessLevel);
BaseDataVariableState valueRankAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "ValueRank",
"ValueRank",
DataTypeIds.Double,
ValueRanks.Scalar);
valueRankAccessLevel.WriteMask = AttributeWriteMask.ValueRank;
valueRankAccessLevel.UserWriteMask = AttributeWriteMask.ValueRank;
variables.Add(valueRankAccessLevel);
BaseDataVariableState writeMaskAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "WriteMask",
"WriteMask",
DataTypeIds.Double,
ValueRanks.Scalar);
writeMaskAccessLevel.WriteMask = AttributeWriteMask.WriteMask;
writeMaskAccessLevel.UserWriteMask = AttributeWriteMask.WriteMask;
variables.Add(writeMaskAccessLevel);
BaseDataVariableState valueForVariableTypeAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "ValueForVariableType",
"ValueForVariableType",
DataTypeIds.Double,
ValueRanks.Scalar);
valueForVariableTypeAccessLevel.WriteMask
= AttributeWriteMask.ValueForVariableType;
valueForVariableTypeAccessLevel.UserWriteMask
= AttributeWriteMask.ValueForVariableType;
variables.Add(valueForVariableTypeAccessLevel);
BaseDataVariableState allAccessLevel = CreateVariable(
folderAttributesAccessAll,
attributesAccessAll + "All",
"All",
DataTypeIds.Double,
ValueRanks.Scalar);
allAccessLevel.WriteMask =
AttributeWriteMask.AccessLevel |
AttributeWriteMask.ArrayDimensions |
AttributeWriteMask.BrowseName |
AttributeWriteMask.ContainsNoLoops |
AttributeWriteMask.DataType |
AttributeWriteMask.Description |
AttributeWriteMask.DisplayName |
AttributeWriteMask.EventNotifier |
AttributeWriteMask.Executable |
AttributeWriteMask.Historizing |
AttributeWriteMask.InverseName |
AttributeWriteMask.IsAbstract |
AttributeWriteMask.MinimumSamplingInterval |
AttributeWriteMask.NodeClass |
AttributeWriteMask.NodeId |
AttributeWriteMask.Symmetric |
AttributeWriteMask.UserAccessLevel |
AttributeWriteMask.UserExecutable |
AttributeWriteMask.UserWriteMask |
AttributeWriteMask.ValueForVariableType |
AttributeWriteMask.ValueRank |
AttributeWriteMask.WriteMask;
allAccessLevel.UserWriteMask =
AttributeWriteMask.AccessLevel |
AttributeWriteMask.ArrayDimensions |
AttributeWriteMask.BrowseName |
AttributeWriteMask.ContainsNoLoops |
AttributeWriteMask.DataType |
AttributeWriteMask.Description |
AttributeWriteMask.DisplayName |
AttributeWriteMask.EventNotifier |
AttributeWriteMask.Executable |
AttributeWriteMask.Historizing |
AttributeWriteMask.InverseName |
AttributeWriteMask.IsAbstract |
AttributeWriteMask.MinimumSamplingInterval |
AttributeWriteMask.NodeClass |
AttributeWriteMask.NodeId |
AttributeWriteMask.Symmetric |
AttributeWriteMask.UserAccessLevel |
AttributeWriteMask.UserExecutable |
AttributeWriteMask.UserWriteMask |
AttributeWriteMask.ValueForVariableType |
AttributeWriteMask.ValueRank |
AttributeWriteMask.WriteMask;
variables.Add(allAccessLevel);
FolderState folderAttributesAccessUser1 = CreateFolder(
folderAttributes,
"Attributes_AccessUser1",
"AccessUser1");
const string attributesAccessUser1 = "Attributes_AccessUser1_";
BaseDataVariableState accessLevelAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "AccessLevel",
"AccessLevel",
DataTypeIds.Double,
ValueRanks.Scalar);
accessLevelAccessAll.WriteMask = AttributeWriteMask.AccessLevel;
accessLevelAccessAll.UserWriteMask = AttributeWriteMask.AccessLevel;
variables.Add(accessLevelAccessAll);
BaseDataVariableState arrayDimensionsAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "ArrayDimensions",
"ArrayDimensions",
DataTypeIds.Double,
ValueRanks.Scalar);
arrayDimensionsAccessUser1.WriteMask = AttributeWriteMask.ArrayDimensions;
arrayDimensionsAccessUser1.UserWriteMask = AttributeWriteMask.ArrayDimensions;
variables.Add(arrayDimensionsAccessUser1);
BaseDataVariableState browseNameAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "BrowseName",
"BrowseName",
DataTypeIds.Double,
ValueRanks.Scalar);
browseNameAccessUser1.WriteMask = AttributeWriteMask.BrowseName;
browseNameAccessUser1.UserWriteMask = AttributeWriteMask.BrowseName;
variables.Add(browseNameAccessUser1);
BaseDataVariableState containsNoLoopsAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "ContainsNoLoops",
"ContainsNoLoops",
DataTypeIds.Double,
ValueRanks.Scalar);
containsNoLoopsAccessUser1.WriteMask = AttributeWriteMask.ContainsNoLoops;
containsNoLoopsAccessUser1.UserWriteMask = AttributeWriteMask.ContainsNoLoops;
variables.Add(containsNoLoopsAccessUser1);
BaseDataVariableState dataTypeAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "DataType",
"DataType",
DataTypeIds.Double,
ValueRanks.Scalar);
dataTypeAccessUser1.WriteMask = AttributeWriteMask.DataType;
dataTypeAccessUser1.UserWriteMask = AttributeWriteMask.DataType;
variables.Add(dataTypeAccessUser1);
BaseDataVariableState descriptionAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "Description",
"Description",
DataTypeIds.Double,
ValueRanks.Scalar);
descriptionAccessUser1.WriteMask = AttributeWriteMask.Description;
descriptionAccessUser1.UserWriteMask = AttributeWriteMask.Description;
variables.Add(descriptionAccessUser1);
BaseDataVariableState eventNotifierAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "EventNotifier",
"EventNotifier",
DataTypeIds.Double,
ValueRanks.Scalar);
eventNotifierAccessUser1.WriteMask = AttributeWriteMask.EventNotifier;
eventNotifierAccessUser1.UserWriteMask = AttributeWriteMask.EventNotifier;
variables.Add(eventNotifierAccessUser1);
BaseDataVariableState executableAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "Executable",
"Executable",
DataTypeIds.Double,
ValueRanks.Scalar);
executableAccessUser1.WriteMask = AttributeWriteMask.Executable;
executableAccessUser1.UserWriteMask = AttributeWriteMask.Executable;
variables.Add(executableAccessUser1);
BaseDataVariableState historizingAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "Historizing",
"Historizing",
DataTypeIds.Double,
ValueRanks.Scalar);
historizingAccessUser1.WriteMask = AttributeWriteMask.Historizing;
historizingAccessUser1.UserWriteMask = AttributeWriteMask.Historizing;
variables.Add(historizingAccessUser1);
BaseDataVariableState inverseNameAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "InverseName",
"InverseName",
DataTypeIds.Double,
ValueRanks.Scalar);
inverseNameAccessUser1.WriteMask = AttributeWriteMask.InverseName;
inverseNameAccessUser1.UserWriteMask = AttributeWriteMask.InverseName;
variables.Add(inverseNameAccessUser1);
BaseDataVariableState isAbstractAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "IsAbstract",
"IsAbstract",
DataTypeIds.Double,
ValueRanks.Scalar);
isAbstractAccessUser1.WriteMask = AttributeWriteMask.IsAbstract;
isAbstractAccessUser1.UserWriteMask = AttributeWriteMask.IsAbstract;
variables.Add(isAbstractAccessUser1);
BaseDataVariableState minimumSamplingIntervalAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "MinimumSamplingInterval",
"MinimumSamplingInterval",
DataTypeIds.Double,
ValueRanks.Scalar);
minimumSamplingIntervalAccessUser1.WriteMask
= AttributeWriteMask.MinimumSamplingInterval;
minimumSamplingIntervalAccessUser1.UserWriteMask
= AttributeWriteMask.MinimumSamplingInterval;
variables.Add(minimumSamplingIntervalAccessUser1);
BaseDataVariableState nodeClassIntervalAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "NodeClass",
"NodeClass",
DataTypeIds.Double,
ValueRanks.Scalar);
nodeClassIntervalAccessUser1.WriteMask = AttributeWriteMask.NodeClass;
nodeClassIntervalAccessUser1.UserWriteMask = AttributeWriteMask.NodeClass;
variables.Add(nodeClassIntervalAccessUser1);
BaseDataVariableState nodeIdAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "NodeId",
"NodeId",
DataTypeIds.Double,
ValueRanks.Scalar);
nodeIdAccessUser1.WriteMask = AttributeWriteMask.NodeId;
nodeIdAccessUser1.UserWriteMask = AttributeWriteMask.NodeId;
variables.Add(nodeIdAccessUser1);
BaseDataVariableState symmetricAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "Symmetric",
"Symmetric",
DataTypeIds.Double,
ValueRanks.Scalar);
symmetricAccessUser1.WriteMask = AttributeWriteMask.Symmetric;
symmetricAccessUser1.UserWriteMask = AttributeWriteMask.Symmetric;
variables.Add(symmetricAccessUser1);
BaseDataVariableState userAccessUser1AccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "UserAccessUser1",
"UserAccessUser1",
DataTypeIds.Double,
ValueRanks.Scalar);
userAccessUser1AccessUser1.WriteMask = AttributeWriteMask.UserAccessLevel;
userAccessUser1AccessUser1.UserWriteMask = AttributeWriteMask.UserAccessLevel;
variables.Add(userAccessUser1AccessUser1);
BaseDataVariableState userExecutableAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "UserExecutable",
"UserExecutable",
DataTypeIds.Double,
ValueRanks.Scalar);
userExecutableAccessUser1.WriteMask = AttributeWriteMask.UserExecutable;
userExecutableAccessUser1.UserWriteMask = AttributeWriteMask.UserExecutable;
variables.Add(userExecutableAccessUser1);
BaseDataVariableState valueRankAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "ValueRank",
"ValueRank",
DataTypeIds.Double,
ValueRanks.Scalar);
valueRankAccessUser1.WriteMask = AttributeWriteMask.ValueRank;
valueRankAccessUser1.UserWriteMask = AttributeWriteMask.ValueRank;
variables.Add(valueRankAccessUser1);
BaseDataVariableState writeMaskAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "WriteMask",
"WriteMask",
DataTypeIds.Double,
ValueRanks.Scalar);
writeMaskAccessUser1.WriteMask = AttributeWriteMask.WriteMask;
writeMaskAccessUser1.UserWriteMask = AttributeWriteMask.WriteMask;
variables.Add(writeMaskAccessUser1);
BaseDataVariableState valueForVariableTypeAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "ValueForVariableType",
"ValueForVariableType",
DataTypeIds.Double,
ValueRanks.Scalar);
valueForVariableTypeAccessUser1.WriteMask
= AttributeWriteMask.ValueForVariableType;
valueForVariableTypeAccessUser1.UserWriteMask
= AttributeWriteMask.ValueForVariableType;
variables.Add(valueForVariableTypeAccessUser1);
BaseDataVariableState allAccessUser1 = CreateVariable(
folderAttributesAccessUser1,
attributesAccessUser1 + "All",
"All",
DataTypeIds.Double,
ValueRanks.Scalar);
allAccessUser1.WriteMask =
AttributeWriteMask.AccessLevel |
AttributeWriteMask.ArrayDimensions |
AttributeWriteMask.BrowseName |
AttributeWriteMask.ContainsNoLoops |
AttributeWriteMask.DataType |
AttributeWriteMask.Description |
AttributeWriteMask.DisplayName |
AttributeWriteMask.EventNotifier |
AttributeWriteMask.Executable |
AttributeWriteMask.Historizing |
AttributeWriteMask.InverseName |
AttributeWriteMask.IsAbstract |
AttributeWriteMask.MinimumSamplingInterval |
AttributeWriteMask.NodeClass |
AttributeWriteMask.NodeId |
AttributeWriteMask.Symmetric |
AttributeWriteMask.UserAccessLevel |
AttributeWriteMask.UserExecutable |
AttributeWriteMask.UserWriteMask |
AttributeWriteMask.ValueForVariableType |
AttributeWriteMask.ValueRank |
AttributeWriteMask.WriteMask;
allAccessUser1.UserWriteMask =
AttributeWriteMask.AccessLevel |
AttributeWriteMask.ArrayDimensions |
AttributeWriteMask.BrowseName |
AttributeWriteMask.ContainsNoLoops |
AttributeWriteMask.DataType |
AttributeWriteMask.Description |
AttributeWriteMask.DisplayName |
AttributeWriteMask.EventNotifier |
AttributeWriteMask.Executable |
AttributeWriteMask.Historizing |
AttributeWriteMask.InverseName |
AttributeWriteMask.IsAbstract |
AttributeWriteMask.MinimumSamplingInterval |
AttributeWriteMask.NodeClass |
AttributeWriteMask.NodeId |
AttributeWriteMask.Symmetric |
AttributeWriteMask.UserAccessLevel |
AttributeWriteMask.UserExecutable |
AttributeWriteMask.UserWriteMask |
AttributeWriteMask.ValueForVariableType |
AttributeWriteMask.ValueRank |
AttributeWriteMask.WriteMask;
variables.Add(allAccessUser1);
ResetRandomGenerator(21);
FolderState myCompanyFolder = CreateFolder(root, "MyCompany", "MyCompany");
const string myCompany = "MyCompany_";
BaseDataVariableState myCompanyInstructions = CreateVariable(
myCompanyFolder,
myCompany + "Instructions",
"Instructions",
DataTypeIds.String,
ValueRanks.Scalar);
myCompanyInstructions.Value
= "A place for the vendor to describe their address-space.";
variables.Add(myCompanyInstructions);
}
catch (Exception e)
{
m_logger.LogError(e, "Error creating the ReferenceNodeManager address space.");
}
await AddPredefinedNodeAsync(SystemContext, root, cancellationToken).ConfigureAwait(false);
// Enable history archiving for selected scalar variables.
await EnableHistoryArchivingAsync(cancellationToken).ConfigureAwait(false);
if (m_simulationEnabled)
{
// reset random generator and generate boundary values
ResetRandomGenerator(100, 1);
TimeProvider timeProvider = (Server as ITimeProviderProvider)?.TimeProvider
?? TimeProvider.System;
m_simulationTimer?.Dispose();
m_simulationTimer = timeProvider.CreateTimer(
DoSimulation,
null,
TimeSpan.FromMilliseconds(m_simulationInterval),
TimeSpan.FromMilliseconds(m_simulationInterval));
}
}
finally
{
m_semaphore.Release();
}
}
private ServiceResult OnWriteInterval(
ISystemContext context,
NodeState node,
ref Variant value)
{
try
{
m_simulationInterval = (ushort)value;
if (m_simulationEnabled)
{
m_simulationTimer!.Change(
TimeSpan.FromMilliseconds(100),
TimeSpan.FromMilliseconds(m_simulationInterval));
}
return ServiceResult.Good;
}
catch (Exception e)
{
m_logger.LogError(e, "Error writing Interval variable.");
return ServiceResult.Create(e, StatusCodes.Bad, "Error writing Interval variable.");
}
}
private ServiceResult OnWriteEnabled(
ISystemContext context,
NodeState node,
ref Variant value)
{
try
{
m_simulationEnabled = (bool)value;
if (m_simulationEnabled)
{
m_simulationTimer!.Change(
TimeSpan.FromMilliseconds(100),
TimeSpan.FromMilliseconds(m_simulationInterval));
}
else
{
m_simulationTimer!.Change(
TimeSpan.FromMilliseconds(100),
TimeSpan.Zero);
}
return ServiceResult.Good;
}
catch (Exception e)
{
m_logger.LogError(e, "Error writing Enabled variable.");
return ServiceResult.Create(e, StatusCodes.Bad, "Error writing Enabled variable.");
}
}
/// <summary>
/// Creates a new folder.
/// </summary>
private FolderState CreateFolder(NodeState? parent, string path, string name)
{
var folder = new FolderState(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypeIds.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
};
parent?.AddChild(folder);
return folder;
}
/// <summary>
/// Creates a new variable.
/// </summary>
private BaseDataVariableState CreateMeshVariable(
NodeState parent,
string path,
string name,
params NodeState[] peers)
{
BaseDataVariableState variable = CreateVariable(
parent,
path,
name,
BuiltInType.Double,
ValueRanks.Scalar);
if (peers != null)
{
foreach (NodeState peer in peers)
{
peer.AddReference(ReferenceTypeIds.HasCause, false, variable.NodeId);
variable.AddReference(ReferenceTypeIds.HasCause, true, peer.NodeId);
peer.AddReference(ReferenceTypeIds.HasEffect, true, variable.NodeId);
variable.AddReference(ReferenceTypeIds.HasEffect, false, peer.NodeId);
}
}
return variable;
}
/// <summary>
/// Creates a new variable.
/// </summary>
private DataItemState CreateDataItemVariable(
FolderState parent,
string path,
string name,
BuiltInType dataType,
int valueRank)
{
var variable = new DataItemState(parent);
variable.ValuePrecision = PropertyState<double>.With<VariantBuilder>(variable);
variable.Definition = PropertyState<string>.With<VariantBuilder>(variable);
variable.Create(SystemContext, default, variable.BrowseName, default, true);
variable.SymbolicName = name;
variable.ReferenceTypeId = ReferenceTypeIds.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 = (NodeId)(uint)dataType;
variable.ValueRank = valueRank;
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = TypeInfo.GetDefaultVariantValue((NodeId)(uint)dataType, valueRank, Server.TypeTree);
variable.StatusCode = StatusCodes.Good;
if (valueRank == ValueRanks.OneDimension)
{
variable.ArrayDimensions = [0];
}
else if (valueRank == ValueRanks.TwoDimensions)
{
variable.ArrayDimensions = [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;
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates a new variable.
/// </summary>
private AnalogItemState CreateAnalogItemVariable(
NodeState parent,
string path,
string name,
BuiltInType dataType,
int valueRank)
{
return CreateAnalogItemVariable(parent, path, name, dataType, valueRank, default);
}
private AnalogItemState CreateAnalogItemVariable(
NodeState parent,
string path,
string name,
BuiltInType dataType,
int valueRank,
Variant initialValues)
{
return CreateAnalogItemVariable(
parent,
path,
name,
dataType,
valueRank,
initialValues,
null);
}
private AnalogItemState CreateAnalogItemVariable(
NodeState parent,
string path,
string name,
BuiltInType dataType,
int valueRank,
Variant initialValues,
Range? customRange)
{
return CreateAnalogItemVariable(
parent,
path,
name,
(NodeId)(uint)dataType,
valueRank,
initialValues,
customRange);
}
private AnalogItemState CreateAnalogItemVariable(
NodeState parent,
string path,
string name,
NodeId dataType,
int valueRank,
Variant initialValues,
Range? customRange)
{
var variable = new AnalogItemState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex)
};
variable.EngineeringUnits = PropertyState<EUInformation>.With<StructureBuilder<EUInformation>>(variable);
variable.InstrumentRange = PropertyState<Range>.With<StructureBuilder<Range>>(variable);
variable.Create(
SystemContext,
new NodeId(path, NamespaceIndex),
variable.BrowseName,
default,
true);
variable.NodeId = new NodeId(path, NamespaceIndex);
variable.SymbolicName = name;
variable.DisplayName = new LocalizedText("en", name);
variable.WriteMask = AttributeWriteMask.None;
variable.UserWriteMask = AttributeWriteMask.None;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = dataType;
variable.ValueRank = valueRank;
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
if (valueRank == ValueRanks.OneDimension)
{
variable.ArrayDimensions = [0];
}
else if (valueRank == ValueRanks.TwoDimensions)
{
variable.ArrayDimensions = [0, 0];
}
BuiltInType builtInType = TypeInfo.GetBuiltInType(dataType, Server.TypeTree);
if (!TypeInfo.IsNumericType(builtInType))
{
throw new ArgumentException("AnalogItem must have a numeric DataType.", nameof(dataType));
}
// Simulate a mV Voltmeter
Range newRange = GetAnalogRange(builtInType);
// Using anything but 120,-10 fails a few tests
newRange.High = Math.Min(newRange.High, 120);
newRange.Low = Math.Max(newRange.Low, -10);
variable.InstrumentRange.Value = newRange;
variable.EURange!.Value = customRange ?? new Range(100, 0);
variable.Value = initialValues;
if (variable.Value.IsNull)
{
variable.Value = TypeInfo.GetDefaultVariantValue(dataType, valueRank, Server.TypeTree);
}
variable.StatusCode = StatusCodes.Good;
// The latest UNECE version (Rev 11, published in 2015) is available here:
// http://www.opcfoundation.org/UA/EngineeringUnits/UNECE/rec20_latest_08052015.zip
variable.EngineeringUnits.Value = new EUInformation(
"mV",
"millivolt",
"http://www.opcfoundation.org/UA/units/un/cefact")
{
// The mapping of the UNECE codes to OPC UA(EUInformation.unitId) is available here:
// http://www.opcfoundation.org/UA/EngineeringUnits/UNECE/UNECE_to_OPCUA.csv
UnitId = 12890 // "2Z"
};
variable.OnWriteValue = OnWriteAnalog;
variable.EURange.OnWriteValue = OnWriteAnalogRange;
variable.EURange.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.EURange.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.EngineeringUnits.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.EngineeringUnits.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.InstrumentRange.OnWriteValue = OnWriteAnalogRange;
variable.InstrumentRange.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.InstrumentRange.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates a new variable.
/// </summary>
private TwoStateDiscreteState CreateTwoStateDiscreteItemVariable(
FolderState parent,
string path,
string name,
string trueState,
string falseState)
{
var variable = new TwoStateDiscreteState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex),
DisplayName = new LocalizedText("en", name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None
};
variable.Create(SystemContext, default, variable.BrowseName, default, true);
variable.SymbolicName = name;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = DataTypeIds.Boolean;
variable.ValueRank = ValueRanks.Scalar;
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = (bool)GetNewValue(variable);
variable.StatusCode = StatusCodes.Good;
variable.TrueState!.Value = LocalizedText.From(trueState);
variable.TrueState.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.TrueState.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.FalseState!.Value = LocalizedText.From(falseState);
variable.FalseState.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.FalseState.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates a new variable.
/// </summary>
private MultiStateDiscreteState CreateMultiStateDiscreteItemVariable(
FolderState parent,
string path,
string name,
params string[] values)
{
var variable = new MultiStateDiscreteState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex),
DisplayName = new LocalizedText("en", name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None
};
variable.Create(SystemContext, default, variable.BrowseName, default, true);
variable.SymbolicName = name;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = DataTypeIds.UInt32;
variable.ValueRank = ValueRanks.Scalar;
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = (uint)0;
variable.StatusCode = StatusCodes.Good;
variable.OnWriteValue = OnWriteDiscrete;
var strings = new LocalizedText[values.Length];
for (int ii = 0; ii < strings.Length; ii++)
{
strings[ii] = LocalizedText.From(values[ii]);
}
variable.EnumStrings!.Value = strings;
variable.EnumStrings.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.EnumStrings.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates a new UInt32 variable.
/// </summary>
private MultiStateValueDiscreteState CreateMultiStateValueDiscreteItemVariable(
NodeState parent,
string path,
string name,
ArrayOf<string> enumNames)
{
return CreateMultiStateValueDiscreteItemVariable(parent, path, name, default, enumNames);
}
/// <summary>
/// Creates a new variable.
/// </summary>
private MultiStateValueDiscreteState CreateMultiStateValueDiscreteItemVariable(
NodeState parent,
string path,
string name,
NodeId nodeId,
ArrayOf<string> enumNames)
{
var variable = new MultiStateValueDiscreteState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex),
DisplayName = new LocalizedText("en", name),
WriteMask = AttributeWriteMask.None,
UserWriteMask = AttributeWriteMask.None
};
variable.Create(SystemContext, default, variable.BrowseName, default, true);
variable.SymbolicName = name;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = nodeId.IsNull ? DataTypeIds.UInt32 : nodeId;
variable.ValueRank = ValueRanks.Scalar;
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = (uint)0;
variable.StatusCode = StatusCodes.Good;
variable.OnWriteValue = OnWriteValueDiscrete;
// there are two enumerations for this type:
// EnumStrings = the string representations for enumerated values
// ValueAsText = the actual enumerated value
// set the enumerated strings
var strings = new LocalizedText[enumNames.Count];
for (int ii = 0; ii < strings.Length; ii++)
{
strings[ii] = LocalizedText.From(enumNames[ii]);
}
// set the enumerated values
var values = new EnumValueType[enumNames.Count];
for (int ii = 0; ii < values.Length; ii++)
{
values[ii] = new EnumValueType
{
Value = ii,
Description = strings[ii],
DisplayName = strings[ii]
};
}
variable.EnumValues!.Value = values;
variable.EnumValues.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.EnumValues.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.ValueAsText!.Value = variable.EnumValues.Value[0].DisplayName;
parent?.AddChild(variable);
return variable;
}
private ServiceResult OnWriteDiscrete(
ISystemContext context,
NodeState node,
NumericRange indexRange,
QualifiedName dataEncoding,
ref Variant value,
ref StatusCode statusCode,
ref DateTimeUtc timestamp)
{
var variable = node as MultiStateDiscreteState;
// verify data type.
var typeInfo = TypeInfo.IsInstanceOfDataType(
value,
variable!.DataType,
variable.ValueRank,
context.NamespaceUris,
context.TypeTable);
if (typeInfo.IsUnknown)
{
return StatusCodes.BadTypeMismatch;
}
if (!indexRange.IsNull)
{
return StatusCodes.BadIndexRangeInvalid;
}
double number = value.GetDouble();
if (number >= variable.EnumStrings!.Value.Count || number < 0)
{
return StatusCodes.BadOutOfRange;
}
return ServiceResult.Good;
}
private ServiceResult OnWriteValueDiscrete(
ISystemContext context,
NodeState node,
NumericRange indexRange,
QualifiedName dataEncoding,
ref Variant value,
ref StatusCode statusCode,
ref DateTimeUtc timestamp)
{
TypeInfo typeInfo = value.TypeInfo;
if (node is not MultiStateValueDiscreteState variable ||
typeInfo.IsUnknown ||
!TypeInfo.IsNumericType(typeInfo.BuiltInType))
{
return StatusCodes.BadTypeMismatch;
}
if (!indexRange.IsNull)
{
return StatusCodes.BadIndexRangeInvalid;
}
int number = (int)value.GetUInt32();
if (number >= variable.EnumValues!.Value.Count || number < 0)
{
return StatusCodes.BadOutOfRange;
}
if (!node.SetChildValue(
context,
BrowseNames.ValueAsText,
variable.EnumValues.Value[number].DisplayName,
true
))
{
return StatusCodes.BadOutOfRange;
}
node.ClearChangeMasks(context, true);
return ServiceResult.Good;
}
private ServiceResult OnWriteAnalog(
ISystemContext context,
NodeState node,
NumericRange indexRange,
QualifiedName dataEncoding,
ref Variant value,
ref StatusCode statusCode,
ref DateTimeUtc timestamp)
{
var variable = node as AnalogItemState;
// verify data type.
var typeInfo = TypeInfo.IsInstanceOfDataType(
value,
variable!.DataType,
variable.ValueRank,
context.NamespaceUris,
context.TypeTable);
if (typeInfo.IsUnknown)
{
return StatusCodes.BadTypeMismatch;
}
// check index range.
if (variable.ValueRank >= 0)
{
if (!indexRange.IsNull)
{
Variant target = variable.Value;
ServiceResult result = indexRange.UpdateRange(ref target, value);
if (ServiceResult.IsBad(result))
{
return result;
}
value = target;
}
}
// check instrument range.
else
{
if (!indexRange.IsNull)
{
return StatusCodes.BadIndexRangeInvalid;
}
double number = value.GetDouble();
if (variable.InstrumentRange != null &&
(number < variable.InstrumentRange.Value.Low ||
number > variable.InstrumentRange.Value.High))
{
return StatusCodes.BadOutOfRange;
}
}
return ServiceResult.Good;
}
private ServiceResult OnWriteAnalogRange(
ISystemContext context,
NodeState node,
NumericRange indexRange,
QualifiedName dataEncoding,
ref Variant value,
ref StatusCode statusCode,
ref DateTimeUtc timestamp)
{
TypeInfo typeInfo = value.TypeInfo;
if (node is not PropertyState<Range> variable ||
!value.TryGetValue(out ExtensionObject extensionObject) ||
typeInfo.IsUnknown)
{
return StatusCodes.BadTypeMismatch;
}
if (!extensionObject.TryGetValue(out Range? newRange) ||
variable.Parent is not AnalogItemState parent)
{
return StatusCodes.BadTypeMismatch;
}
if (!indexRange.IsNull)
{
return StatusCodes.BadIndexRangeInvalid;
}
TypeInfo parentTypeInfo = parent.Value.TypeInfo;
Range parentRange = GetAnalogRange(parentTypeInfo.BuiltInType);
if (parentRange.High < newRange.High || parentRange.Low > newRange.Low)
{
return StatusCodes.BadOutOfRange;
}
value = Variant.FromStructure(newRange);
return ServiceResult.Good;
}
/// <summary>
/// Fires a base event whenever the trigger node is written to.
/// </summary>
private ServiceResult OnWriteTriggerNode(
ISystemContext context,
NodeState node,
ref Variant value)
{
var e = new BaseEventState(null);
e.Initialize(
context,
node,
EventSeverity.Medium,
new LocalizedText($"Trigger event from '{node.DisplayName.Text}'"));
Server.ReportEvent(context, e);
return ServiceResult.Good;
}
/// <summary>
/// Creates a default <see cref="AxisInformation"/> instance with the given title.
/// </summary>
private static AxisInformation CreateDefaultAxisInformation(string title)
{
return new()
{
EngineeringUnits = new EUInformation("s", "seconds", "http://www.opcfoundation.org/UA/units/un/cefact"),
EURange = new Range(100, 0),
Title = new LocalizedText("en", title),
AxisScaleType = AxisScaleEnumeration.Linear
};
}
/// <summary>
/// Applies common read/write access settings to a mandatory child property of an array item variable.
/// </summary>
/// <typeparam name="T"></typeparam>
private static void SetArrayItemChildAccess<T>(PropertyState<T> property)
{
if (property != null)
{
property.AccessLevel = AccessLevels.CurrentReadOrWrite;
property.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
}
}
/// <summary>
/// Creates and adds a <see cref="YArrayItemState"/> variable to the address space.
/// </summary>
private YArrayItemState CreateYArrayItemVariable(NodeState parent, string path, string name)
{
var variable = new YArrayItemState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex)
};
variable.Create(
SystemContext,
new NodeId(path, NamespaceIndex),
variable.BrowseName,
default,
true);
variable.NodeId = new NodeId(path, NamespaceIndex);
variable.SymbolicName = name;
variable.DisplayName = new LocalizedText("en", name);
variable.WriteMask = AttributeWriteMask.None;
variable.UserWriteMask = AttributeWriteMask.None;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = DataTypeIds.Double;
variable.ValueRank = ValueRanks.OneDimension;
variable.ArrayDimensions = [0];
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = Variant.From(s_doubleArray);
variable.StatusCode = StatusCodes.Good;
if (variable.XAxisDefinition != null)
{
variable.XAxisDefinition.Value = CreateDefaultAxisInformation("X Axis");
SetArrayItemChildAccess(variable.XAxisDefinition);
}
if (variable.EURange != null)
{
variable.EURange.Value = new Range(100, 0);
SetArrayItemChildAccess(variable.EURange);
}
if (variable.InstrumentRange != null)
{
variable.InstrumentRange.Value = new Range(120, -10);
SetArrayItemChildAccess(variable.InstrumentRange);
}
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates and adds a <see cref="XYArrayItemState"/> variable to the address space.
/// </summary>
private XYArrayItemState CreateXYArrayItemVariable(NodeState parent, string path, string name)
{
var variable = new XYArrayItemState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex)
};
variable.Create(
SystemContext,
new NodeId(path, NamespaceIndex),
variable.BrowseName,
default,
true);
variable.NodeId = new NodeId(path, NamespaceIndex);
variable.SymbolicName = name;
variable.DisplayName = new LocalizedText("en", name);
variable.WriteMask = AttributeWriteMask.None;
variable.UserWriteMask = AttributeWriteMask.None;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = new NodeId(DataTypes.XVType);
variable.ValueRank = ValueRanks.OneDimension;
variable.ArrayDimensions = [0];
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = Variant.FromStructure(ArrayOf.Wrapped(
new XVType { X = 0.0, Value = 0.0f },
new XVType { X = 1.0, Value = 1.0f },
new XVType { X = 2.0, Value = 4.0f },
new XVType { X = 3.0, Value = 9.0f },
new XVType { X = 4.0, Value = 16.0f }
));
variable.StatusCode = StatusCodes.Good;
if (variable.XAxisDefinition != null)
{
variable.XAxisDefinition.Value = CreateDefaultAxisInformation("X Axis");
SetArrayItemChildAccess(variable.XAxisDefinition);
}
if (variable.EURange != null)
{
variable.EURange.Value = new Range(100, 0);
SetArrayItemChildAccess(variable.EURange);
}
if (variable.InstrumentRange != null)
{
variable.InstrumentRange.Value = new Range(120, -10);
SetArrayItemChildAccess(variable.InstrumentRange);
}
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates and adds an <see cref="ImageItemState"/> variable to the address space.
/// </summary>
private ImageItemState CreateImageItemVariable(NodeState parent, string path, string name)
{
var variable = new ImageItemState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex)
};
variable.Create(
SystemContext,
new NodeId(path, NamespaceIndex),
variable.BrowseName,
default,
true);
variable.NodeId = new NodeId(path, NamespaceIndex);
variable.SymbolicName = name;
variable.DisplayName = new LocalizedText("en", name);
variable.WriteMask = AttributeWriteMask.None;
variable.UserWriteMask = AttributeWriteMask.None;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = DataTypeIds.Double;
variable.ValueRank = ValueRanks.TwoDimensions;
variable.ArrayDimensions = [0, 0];
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = Variant.From(
MatrixOf<double>.CreateFromArray(new double[,]
{
{ 0.0, 1.0, 2.0 },
{ 3.0, 4.0, 5.0 }
}));
variable.StatusCode = StatusCodes.Good;
if (variable.XAxisDefinition != null)
{
variable.XAxisDefinition.Value = CreateDefaultAxisInformation("X Axis");
SetArrayItemChildAccess(variable.XAxisDefinition);
}
if (variable.YAxisDefinition != null)
{
variable.YAxisDefinition.Value = CreateDefaultAxisInformation("Y Axis");
SetArrayItemChildAccess(variable.YAxisDefinition);
}
if (variable.EURange != null)
{
variable.EURange.Value = new Range(100, 0);
SetArrayItemChildAccess(variable.EURange);
}
if (variable.InstrumentRange != null)
{
variable.InstrumentRange.Value = new Range(120, -10);
SetArrayItemChildAccess(variable.InstrumentRange);
}
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates and adds a <see cref="CubeItemState"/> variable to the address space.
/// </summary>
private CubeItemState CreateCubeItemVariable(NodeState parent, string path, string name)
{
var variable = new CubeItemState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex)
};
variable.Create(
SystemContext,
new NodeId(path, NamespaceIndex),
variable.BrowseName,
default,
true);
variable.NodeId = new NodeId(path, NamespaceIndex);
variable.SymbolicName = name;
variable.DisplayName = new LocalizedText("en", name);
variable.WriteMask = AttributeWriteMask.None;
variable.UserWriteMask = AttributeWriteMask.None;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = DataTypeIds.Double;
variable.ValueRank = 3;
variable.ArrayDimensions = [0, 0, 0];
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = Variant.From(
MatrixOf<double>.CreateFromArray(new double[,,]
{
{ { 0.0, 1.0 }, { 2.0, 3.0 } },
{ { 4.0, 5.0 }, { 6.0, 7.0 } }
}));
variable.StatusCode = StatusCodes.Good;
if (variable.XAxisDefinition != null)
{
variable.XAxisDefinition.Value = CreateDefaultAxisInformation("X Axis");
SetArrayItemChildAccess(variable.XAxisDefinition);
}
if (variable.YAxisDefinition != null)
{
variable.YAxisDefinition.Value = CreateDefaultAxisInformation("Y Axis");
SetArrayItemChildAccess(variable.YAxisDefinition);
}
if (variable.ZAxisDefinition != null)
{
variable.ZAxisDefinition.Value = CreateDefaultAxisInformation("Z Axis");
SetArrayItemChildAccess(variable.ZAxisDefinition);
}
if (variable.EURange != null)
{
variable.EURange.Value = new Range(100, 0);
SetArrayItemChildAccess(variable.EURange);
}
if (variable.InstrumentRange != null)
{
variable.InstrumentRange.Value = new Range(120, -10);
SetArrayItemChildAccess(variable.InstrumentRange);
}
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates and adds an <see cref="NDimensionArrayItemState"/> variable to the address space.
/// </summary>
private NDimensionArrayItemState CreateNDimensionArrayItemVariable(NodeState parent, string path, string name)
{
var variable = new NDimensionArrayItemState(parent)
{
BrowseName = new QualifiedName(path, NamespaceIndex)
};
variable.Create(
SystemContext,
new NodeId(path, NamespaceIndex),
variable.BrowseName,
default,
true);
variable.NodeId = new NodeId(path, NamespaceIndex);
variable.SymbolicName = name;
variable.DisplayName = new LocalizedText("en", name);
variable.WriteMask = AttributeWriteMask.None;
variable.UserWriteMask = AttributeWriteMask.None;
variable.ReferenceTypeId = ReferenceTypeIds.Organizes;
variable.DataType = DataTypeIds.Double;
variable.ValueRank = ValueRanks.TwoDimensions;
variable.ArrayDimensions = [0, 0];
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
variable.Historizing = false;
variable.Value = Variant.From(
MatrixOf<double>.CreateFromArray(new double[,]
{
{ 0.0, 1.0, 2.0 },
{ 3.0, 4.0, 5.0 }
}));
variable.StatusCode = StatusCodes.Good;
if (variable.AxisDefinition != null)
{
variable.AxisDefinition.Value = new AxisInformation[]
{
CreateDefaultAxisInformation("X Axis"),
CreateDefaultAxisInformation("Y Axis")
}.ToArrayOf();
SetArrayItemChildAccess(variable.AxisDefinition);
}
if (variable.EURange != null)
{
variable.EURange.Value = new Range(100, 0);
SetArrayItemChildAccess(variable.EURange);
}
if (variable.InstrumentRange != null)
{
variable.InstrumentRange.Value = new Range(120, -10);
SetArrayItemChildAccess(variable.InstrumentRange);
}
parent?.AddChild(variable);
return variable;
}
/// <summary>
/// Creates a new variable.
/// </summary>
private BaseDataVariableState CreateVariable(
NodeState parent,
string path,
string name,
BuiltInType dataType,
int valueRank)
{
return CreateVariable(parent, path, name, (NodeId)(uint)dataType, valueRank);
}
/// <summary>
/// Creates a new variable.
/// </summary>
private BaseDataVariableState CreateVariable(
NodeState parent,
string path,
string name,
NodeId dataType,
int valueRank)
{
var variable = new BaseDataVariableState(parent)
{
SymbolicName = name,
ReferenceTypeId = ReferenceTypeIds.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
};
variable.Value = GetNewValue(variable);
variable.StatusCode = StatusCodes.Good;
variable.Description = LocalizedText.From("Default Description");
if (valueRank == ValueRanks.OneDimension)
{
variable.ArrayDimensions = [0];
}
else if (valueRank == ValueRanks.TwoDimensions)
{
variable.ArrayDimensions = [0, 0];
}
parent?.AddChild(variable);
return variable;
}
private BaseDataVariableState[] CreateVariables(
NodeState parent,
string path,
string name,
BuiltInType dataType,
int valueRank,
ushort numVariables)
{
return CreateVariables(parent, path, name, (NodeId)(uint)dataType, valueRank, numVariables);
}
private BaseDataVariableState[] CreateVariables(
NodeState parent,
string path,
string name,
NodeId dataType,
int valueRank,
ushort numVariables)
{
// first, create a new Parent folder for this data-type
FolderState newParentFolder = CreateFolder(parent, path, name);
var itemsCreated = new List<BaseDataVariableState>();
// now to create the remaining NUMBERED items
for (uint i = 0; i < numVariables; i++)
{
string newName = string.Format(
CultureInfo.InvariantCulture,
"{0}_{1}",
name,
i.ToString("00", CultureInfo.InvariantCulture));
string newPath = string.Format(
CultureInfo.InvariantCulture,
"{0}_{1}",
path,
newName);
itemsCreated.Add(
CreateVariable(newParentFolder, newPath, newName, dataType, valueRank));
}
return [.. itemsCreated];
}
/// <summary>
/// Creates a new variable.
/// </summary>
private BaseDataVariableState CreateDynamicVariable(
NodeState parent,
string path,
string name,
BuiltInType dataType,
int valueRank)
{
return CreateDynamicVariable(parent, path, name, (NodeId)(uint)dataType, valueRank);
}
/// <summary>
/// Creates a new variable.
/// </summary>
private BaseDataVariableState CreateDynamicVariable(
NodeState parent,
string path,
string name,
NodeId dataType,
int valueRank)
{
BaseDataVariableState variable = CreateVariable(
parent,
path,
name,
dataType,
valueRank);
m_dynamicNodes.Add(variable);
return variable;
}
private BaseDataVariableState[] CreateDynamicVariables(
NodeState parent,
string path,
string name,
BuiltInType dataType,
int valueRank,
uint numVariables)
{
return CreateDynamicVariables(
parent,
path,
name,
(NodeId)(uint)dataType,
valueRank,
numVariables);
}
private BaseDataVariableState[] CreateDynamicVariables(
NodeState parent,
string path,
string name,
NodeId dataType,
int valueRank,
uint numVariables)
{
// first, create a new Parent folder for this data-type
FolderState newParentFolder = CreateFolder(parent, path, name);
var itemsCreated = new List<BaseDataVariableState>();
// now to create the remaining NUMBERED items
for (uint i = 0; i < numVariables; i++)
{
string newName = string.Format(
CultureInfo.InvariantCulture,
"{0}_{1}",
name,
i.ToString("00", CultureInfo.InvariantCulture));
string newPath = string.Format(
CultureInfo.InvariantCulture,
"{0}_{1}",
path,
newName);
itemsCreated.Add(
CreateDynamicVariable(newParentFolder, newPath, newName, dataType, valueRank));
} //for i
return [.. itemsCreated];
}
/// <summary>
/// Creates a new view.
/// </summary>
private async ValueTask<ViewState> CreateViewAsync(
NodeState parent,
IDictionary<NodeId, IList<IReference>> externalReferences,
string path,
string name,
CancellationToken cancellationToken = default)
{
var type = new ViewState
{
SymbolicName = name,
NodeId = new NodeId(path, NamespaceIndex),
BrowseName = new QualifiedName(name, NamespaceIndex)
};
type.DisplayName = LocalizedText.From(type.BrowseName.Name!);
type.WriteMask = AttributeWriteMask.None;
type.UserWriteMask = AttributeWriteMask.None;
type.ContainsNoLoops = true;
if (!externalReferences.TryGetValue(
ObjectIds.ViewsFolder,
out IList<IReference>? references))
{
externalReferences[ObjectIds.ViewsFolder] = references = [];
}
type.AddReference(ReferenceTypeIds.Organizes, true, ObjectIds.ViewsFolder);
references.Add(new NodeStateReference(ReferenceTypeIds.Organizes, false, type.NodeId));
if (parent != null)
{
parent.AddReference(ReferenceTypeIds.Organizes, false, type.NodeId);
type.AddReference(ReferenceTypeIds.Organizes, true, parent.NodeId);
}
await AddPredefinedNodeAsync(SystemContext, type, cancellationToken).ConfigureAwait(false);
return type;
}
/// <summary>
/// Creates a new method.
/// </summary>
private MethodState CreateMethod(FolderState parent, string path, string name)
{
var 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
};
parent?.AddChild(method);
return method;
}
private ServiceResult OnVoidCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
return ServiceResult.Good;
}
private ServiceResult OnAddCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
// all arguments must be provided.
if (inputArguments.Count < 2)
{
return StatusCodes.BadArgumentsMissing;
}
try
{
float floatValue = (float)inputArguments[0];
uint uintValue = (uint)inputArguments[1];
// set output parameter
outputArguments[0] = floatValue + uintValue;
return ServiceResult.Good;
}
catch
{
return new ServiceResult(StatusCodes.BadInvalidArgument);
}
}
private ServiceResult OnMultiplyCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
// all arguments must be provided.
if (inputArguments.Count < 2)
{
return StatusCodes.BadArgumentsMissing;
}
try
{
short op1 = (short)inputArguments[0];
ushort op2 = (ushort)inputArguments[1];
// set output parameter
outputArguments[0] = op1 * op2;
return ServiceResult.Good;
}
catch
{
return new ServiceResult(StatusCodes.BadInvalidArgument);
}
}
private ServiceResult OnDivideCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
// all arguments must be provided.
if (inputArguments.Count < 2)
{
return StatusCodes.BadArgumentsMissing;
}
try
{
int op1 = (int)inputArguments[0];
ushort op2 = (ushort)inputArguments[1];
// set output parameter
outputArguments[0] = op1 / (float)op2;
return ServiceResult.Good;
}
catch
{
return new ServiceResult(StatusCodes.BadInvalidArgument);
}
}
private ServiceResult OnSubstractCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
// all arguments must be provided.
if (inputArguments.Count < 2)
{
return StatusCodes.BadArgumentsMissing;
}
try
{
short op1 = (short)inputArguments[0];
byte op2 = (byte)inputArguments[1];
// set output parameter
outputArguments[0] = (short)(op1 - op2);
return ServiceResult.Good;
}
catch
{
return new ServiceResult(StatusCodes.BadInvalidArgument);
}
}
private ServiceResult OnHelloCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
// all arguments must be provided.
if (inputArguments.Count < 1)
{
return StatusCodes.BadArgumentsMissing;
}
try
{
string op1 = (string)inputArguments[0];
// set output parameter
outputArguments[0] = "hello " + op1;
return ServiceResult.Good;
}
catch
{
return new ServiceResult(StatusCodes.BadInvalidArgument);
}
}
private ServiceResult OnInputCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
// all arguments must be provided.
if (inputArguments.Count < 1)
{
return StatusCodes.BadArgumentsMissing;
}
return ServiceResult.Good;
}
private ServiceResult OnOutputCall(
ISystemContext context,
MethodState method,
ArrayOf<Variant> inputArguments,
List<Variant> outputArguments)
{
// all arguments must be provided.
try
{
// set output parameter
outputArguments[0] = "Output";
return ServiceResult.Good;
}
catch
{
return new ServiceResult(StatusCodes.BadInvalidArgument);
}
}
private void ResetRandomGenerator(int seed, int boundaryValueFrequency = 0)
{
m_randomSource = new RandomSource(seed);
m_generator = new DataGenerator(m_randomSource, Server.Telemetry)
{
BoundaryValueFrequency = boundaryValueFrequency
};
}
private Variant GetNewValue(BaseVariableState variable)
{
Debug.Assert(m_generator != null, "Need a random generator!");
Variant value = default;
for (int retryCount = 0; value.IsNull && retryCount < 10; retryCount++)
{
value = m_generator!.GetRandom(
variable.DataType,
variable.ValueRank,
[10],
Server.TypeTree);
}
return value;
}
private void DoSimulation(object? state)
{
if (!m_simulationEnabled)
{
return;
}
int running = Interlocked.Increment(ref m_simulationsRunning);
try
{
if (running > 0)
{
LogLevel logLevel = running > 1 ?
running > 4 ? LogLevel.Warning : LogLevel.Information :
LogLevel.Debug;
m_logger.Log(logLevel,
"Simulation timer fired while {Count} simulations are already queued to run.",
running);
}
m_semaphore.Wait();
try
{
DateTimeUtc timeStamp = DateTimeUtc.Now;
foreach (BaseDataVariableState variable in m_dynamicNodes)
{
variable.Value = GetNewValue(variable);
variable.Timestamp = timeStamp;
variable.ClearChangeMasks(SystemContext, false);
}
}
finally
{
m_semaphore.Release();
}
}
catch (Exception e)
{
m_logger.LogError(e, "Unexpected error doing simulation #{Count}.", running);
}
finally
{
Interlocked.Decrement(ref m_simulationsRunning);
}
}
/// <summary>
/// Frees any resources allocated for the address space.
/// </summary>
public override ValueTask DeleteAddressSpaceAsync(CancellationToken cancellationToken = default)
{
// TBD
return new ValueTask();
}
/// <summary>
/// Returns a unique handle for the node.
/// </summary>
protected override ValueTask<NodeHandle> GetManagerHandleAsync(
ServerSystemContext context,
NodeId nodeId,
IDictionary<NodeId, NodeState> cache,
CancellationToken cancellationToken = default)
{
// quickly exclude nodes that are not in the namespace.
if (!IsNodeIdInNamespace(nodeId))
{
return default;
}
if (!PredefinedNodes.TryGetValue(nodeId, out NodeState? node))
{
return default;
}
return new ValueTask<NodeHandle>(new NodeHandle
{
NodeId = nodeId,
Node = node,
Validated = true
});
}
/// <summary>
/// Verifies that the specified node exists.
/// </summary>
protected override ValueTask<NodeState> ValidateNodeAsync(
ServerSystemContext context,
NodeHandle handle,
IDictionary<NodeId, NodeState> cache,
CancellationToken cancellationToken = default)
{
// not valid if no root.
if (handle == null)
{
return default;
}
// check if previously validated.
if (handle.Validated)
{
return new ValueTask<NodeState>(handle.Node);
}
// TBD
return default;
}
private readonly SemaphoreSlim m_semaphore = new(1, 1);
private RandomSource m_randomSource = null!;
private DataGenerator m_generator = null!;
private ITimer? m_simulationTimer;
private ushort m_simulationInterval = 1000;
private bool m_simulationEnabled = true;
private int m_simulationsRunning;
private readonly List<BaseDataVariableState> m_dynamicNodes = [];
private InMemoryHistorianProvider? m_historian;
/// <summary>
/// Identifiers of the nodes that support history archiving.
/// </summary>
private static readonly string[] HistoricalNodeNames =
[
"Scalar_Static_Double",
"Scalar_Static_Int32",
"Scalar_Static_Float"
];
/// <inheritdoc/>
protected override IHistorianProvider? GetHistorianProvider(NodeState node)
{
return m_historian;
}
/// <summary>
/// Enables history archiving on selected scalar variables using
/// the fluent <see cref="HistorianBuilder"/> API.
/// </summary>
private async Task EnableHistoryArchivingAsync(CancellationToken cancellationToken)
{
m_historian = new InMemoryHistorianProvider();
// Make the provider discoverable to the server-wide registry so
// server capabilities (HistoryServerCapabilities) reflect what the
// provider supports. The dispatcher will still prefer the
// per-node-manager override returned by GetHistorianProvider.
if (Server is IHistorianRegistryProvider registry)
{
registry.HistorianRegistry.RegisterDefault(m_historian);
}
foreach (string name in HistoricalNodeNames)
{
var nodeId = new NodeId(name, NamespaceIndex);
if (!PredefinedNodes.TryGetValue(nodeId, out NodeState? node))
{
continue;
}
if (node is not BaseVariableState variable)
{
continue;
}
variable.Historizing = true;
variable.AccessLevel = (byte)(variable.AccessLevel | AccessLevels.HistoryRead | AccessLevels.HistoryWrite);
variable.UserAccessLevel = (byte)(variable.UserAccessLevel | AccessLevels.HistoryRead | AccessLevels.HistoryWrite);
m_historian.Register(nodeId);
await SeedHistoricalNodeAsync(nodeId, TypeInfo.GetBuiltInType(variable.DataType), cancellationToken).ConfigureAwait(false);
}
}
private async Task SeedHistoricalNodeAsync(NodeId nodeId, BuiltInType dataType, CancellationToken cancellationToken)
{
DateTime now = DateTime.UtcNow;
var seed = new List<DataValue>(1001);
for (int ii = 1000; ii >= 0; ii--)
{
int value = 1000 - ii;
Variant variant = dataType switch
{
BuiltInType.Int32 => new Variant(value),
BuiltInType.Float => new Variant((float)value),
BuiltInType.Double => new Variant((double)value),
_ => new Variant(value)
};
seed.Add(new DataValue(
variant,
StatusCodes.Good,
sourceTimestamp: now.AddSeconds(-(ii * 10)).AddMilliseconds(1234),
serverTimestamp: now.AddSeconds(-(ii * 10))));
}
var opContext = new OperationContext(new RequestHeader(), null, RequestType.HistoryUpdate, RequestLifetime.None);
var systemContext = new ServerSystemContext(Server, opContext);
var historianContext = new HistorianOperationContext(systemContext, opContext, null, HistoryUpdateType.Insert);
_ = await m_historian!.InsertAsync(historianContext, nodeId, seed, cancellationToken).ConfigureAwait(false);
}
private static readonly ArrayOf<double> s_doubleArray =
[
9.00001d,
9.0002d,
9.003d,
9.04d,
9.5d,
9.06d,
9.007d,
9.008d,
9.0009d
];
private static readonly ArrayOf<float> s_singleArray
= [0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f];
private static readonly ArrayOf<short> s_shortArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
private static readonly ArrayOf<int> s_int32Array = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
private static readonly ArrayOf<string> s_stringArray1 = ["open", "closed", "jammed"];
private static readonly ArrayOf<string> s_stringArray2 = ["red", "green", "blue", "cyan"];
private static readonly ArrayOf<string> s_stringArray3 = ["lolo", "lo", "normal", "hi", "hihi"];
private static readonly ArrayOf<string> s_stringArray4 = ["left", "right", "center"];
private static readonly ArrayOf<string> s_stringArray5 = ["circle", "cross", "triangle"];
private static readonly ArrayOf<string> s_stringArray6 = ["open", "closed", "jammed"];
private static readonly ArrayOf<string> s_stringArray7 = ["red", "green", "blue", "cyan"];
private static readonly ArrayOf<string> s_stringArray8 = ["lolo", "lo", "normal", "hi", "hihi"];
private static readonly ArrayOf<string> s_stringArray9 = ["left", "right", "center"];
}
public static class VariableExtensions
{
public static BaseDataVariableState MinimumSamplingInterval(
this BaseDataVariableState variable,
int minimumSamplingInterval)
{
variable.MinimumSamplingInterval = minimumSamplingInterval;
return variable;
}
}
}