mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 22:55:08 +08:00
294 lines
10 KiB
C#
294 lines
10 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.Globalization;
|
|
using System.Threading;
|
|
using Opc.Ua;
|
|
using Range = Opc.Ua.Range;
|
|
|
|
namespace TestData
|
|
{
|
|
public partial class TestDataObjectState
|
|
{
|
|
/// <summary>
|
|
/// Initializes the object as a collection of counters which change value on read.
|
|
/// </summary>
|
|
protected override void OnAfterCreate(ISystemContext context, NodeState node, CancellationToken ct = default)
|
|
{
|
|
base.OnAfterCreate(context, node, ct);
|
|
|
|
GenerateValues!.OnCall = OnGenerateValues;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialzies the variable as a counter.
|
|
/// </summary>
|
|
protected void InitializeVariable(
|
|
ISystemContext context,
|
|
BaseVariableState variable,
|
|
uint numericId)
|
|
{
|
|
variable.NumericId = numericId;
|
|
|
|
// provide an implementation that produces a random value on each read.
|
|
if (SimulationActive!.Value)
|
|
{
|
|
variable.OnReadValue = DoDeviceRead;
|
|
// Skip expensive initial value generation for simulated variables.
|
|
// DoDeviceRead produces fresh values on demand; the initial value
|
|
// from the generated code or a null is sufficient here.
|
|
}
|
|
else if (context.SystemHandle is TestDataSystem system)
|
|
{
|
|
// Generate initial values only for non-simulated (static) variables
|
|
// that don't have a DoDeviceRead callback. Use reduced complexity
|
|
// to speed up address space creation.
|
|
int savedMaxArrayLength = system.Generator.MaxArrayLength;
|
|
int savedMaxStringLength = system.Generator.MaxStringLength;
|
|
int savedMaxXmlElementCount = system.Generator.MaxXmlElementCount;
|
|
int savedMaxXmlAttributeCount = system.Generator.MaxXmlAttributeCount;
|
|
system.Generator.MaxArrayLength = 5;
|
|
system.Generator.MaxStringLength = 10;
|
|
system.Generator.MaxXmlElementCount = 2;
|
|
system.Generator.MaxXmlAttributeCount = 2;
|
|
try
|
|
{
|
|
GenerateValue(system, variable);
|
|
}
|
|
finally
|
|
{
|
|
system.Generator.MaxArrayLength = savedMaxArrayLength;
|
|
system.Generator.MaxStringLength = savedMaxStringLength;
|
|
system.Generator.MaxXmlElementCount = savedMaxXmlElementCount;
|
|
system.Generator.MaxXmlAttributeCount = savedMaxXmlAttributeCount;
|
|
}
|
|
}
|
|
|
|
// allow writes if the simulation is not active.
|
|
if (!SimulationActive.Value)
|
|
{
|
|
variable.AccessLevel = AccessLevels.CurrentReadOrWrite;
|
|
variable.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
|
|
|
|
var children = new List<BaseInstanceState>();
|
|
variable.GetChildren(context, children);
|
|
foreach (BaseInstanceState child in children)
|
|
{
|
|
if (child is BaseVariableState variableChild)
|
|
{
|
|
variableChild.AccessLevel = AccessLevels.CurrentReadOrWrite;
|
|
variableChild.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the EU range.
|
|
|
|
if (variable.FindChild(
|
|
context,
|
|
QualifiedName.From(Opc.Ua.BrowseNames.EURange)) is BaseVariableState euRange)
|
|
{
|
|
if (context.TypeTable.IsTypeOf(variable.DataType, Opc.Ua.DataTypeIds.UInteger))
|
|
{
|
|
euRange.Value = Variant.FromStructure(new Range(250, 50));
|
|
}
|
|
else
|
|
{
|
|
euRange.Value = Variant.FromStructure(new Range(100, -100));
|
|
}
|
|
variable.OnSimpleWriteValue = OnWriteAnalogValue;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates a written value.
|
|
/// </summary>
|
|
public ServiceResult OnWriteAnalogValue(
|
|
ISystemContext context,
|
|
NodeState node,
|
|
ref Variant value)
|
|
{
|
|
try
|
|
{
|
|
if (node.FindChild(
|
|
context,
|
|
QualifiedName.From(Opc.Ua.BrowseNames.EURange)) is not BaseVariableState euRange)
|
|
{
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
Range? range;
|
|
if (!euRange.Value.TryGetStructure<Range>(out range!))
|
|
{
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
if (value.AsBoxedObject() is Array array)
|
|
{
|
|
for (int ii = 0; ii < array.Length; ii++)
|
|
{
|
|
object? element = array.GetValue(ii);
|
|
|
|
if (typeof(Variant).IsInstanceOfType(element))
|
|
{
|
|
element = ((Variant)element!).AsBoxedObject();
|
|
}
|
|
|
|
double elementNumber = Convert.ToDouble(
|
|
element,
|
|
CultureInfo.InvariantCulture);
|
|
|
|
if (elementNumber > range.High || elementNumber < range.Low)
|
|
{
|
|
return StatusCodes.BadOutOfRange;
|
|
}
|
|
}
|
|
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
double number = value.GetDouble();
|
|
|
|
if (number > range.High || number < range.Low)
|
|
{
|
|
return StatusCodes.BadOutOfRange;
|
|
}
|
|
|
|
return ServiceResult.Good;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new value for the variable.
|
|
/// </summary>
|
|
protected void GenerateValue(TestDataSystem system, BaseVariableState variable)
|
|
{
|
|
variable.Value = system.ReadValue(variable);
|
|
variable.Timestamp = DateTime.UtcNow;
|
|
variable.StatusCode = StatusCodes.Good;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the generate values method.
|
|
/// </summary>
|
|
protected virtual ServiceResult OnGenerateValues(
|
|
ISystemContext context,
|
|
MethodState method,
|
|
NodeId objectId,
|
|
uint count)
|
|
{
|
|
ClearChangeMasks(context, true);
|
|
|
|
if (AreEventsMonitored)
|
|
{
|
|
var e = new GenerateValuesEventState(null);
|
|
|
|
var message = new TranslationInfo(
|
|
"GenerateValuesEventType",
|
|
"en-US",
|
|
"New values generated for test source '{0}'.",
|
|
DisplayName);
|
|
|
|
e.Initialize(context, this, EventSeverity.MediumLow, new LocalizedText(message));
|
|
|
|
e.Iterations = PropertyState<uint>.With<VariantBuilder>(e, count);
|
|
|
|
e.NewValueCount = PropertyState<uint>.With<VariantBuilder>(e, 10);
|
|
|
|
ReportEvent(context, e);
|
|
}
|
|
|
|
#if CONDITION_SAMPLES
|
|
this.CycleComplete.RequestAcknowledgement(context, (ushort)EventSeverity.Low);
|
|
#endif
|
|
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new value each time the value is read.
|
|
/// </summary>
|
|
private ServiceResult DoDeviceRead(
|
|
ISystemContext context,
|
|
NodeState node,
|
|
NumericRange indexRange,
|
|
QualifiedName dataEncoding,
|
|
ref Variant value,
|
|
ref StatusCode statusCode,
|
|
ref DateTimeUtc timestamp)
|
|
{
|
|
if (node is not BaseVariableState variable)
|
|
{
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
if (!SimulationActive!.Value)
|
|
{
|
|
return ServiceResult.Good;
|
|
}
|
|
|
|
if (context.SystemHandle is not TestDataSystem system)
|
|
{
|
|
return StatusCodes.BadOutOfService;
|
|
}
|
|
|
|
try
|
|
{
|
|
value = system.ReadValue(variable);
|
|
|
|
statusCode = StatusCodes.Good;
|
|
timestamp = DateTimeUtc.Now;
|
|
|
|
ServiceResult error = BaseVariableState.ApplyIndexRangeAndDataEncoding(
|
|
context,
|
|
indexRange,
|
|
dataEncoding,
|
|
ref value);
|
|
|
|
if (ServiceResult.IsBad(error))
|
|
{
|
|
statusCode = error.StatusCode;
|
|
}
|
|
|
|
return ServiceResult.Good;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return new ServiceResult(e);
|
|
}
|
|
}
|
|
}
|
|
}
|