/* ======================================================================== * 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 { /// /// Initializes the object as a collection of counters which change value on read. /// protected override void OnAfterCreate(ISystemContext context, NodeState node, CancellationToken ct = default) { base.OnAfterCreate(context, node, ct); GenerateValues!.OnCall = OnGenerateValues; } /// /// Initialzies the variable as a counter. /// 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(); 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; } } /// /// Validates a written value. /// 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(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; } } /// /// Generates a new value for the variable. /// protected void GenerateValue(TestDataSystem system, BaseVariableState variable) { variable.Value = system.ReadValue(variable); variable.Timestamp = DateTime.UtcNow; variable.StatusCode = StatusCodes.Good; } /// /// Handles the generate values method. /// 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.With(e, count); e.NewValueCount = PropertyState.With(e, 10); ReportEvent(context, e); } #if CONDITION_SAMPLES this.CycleComplete.RequestAcknowledgement(context, (ushort)EventSeverity.Low); #endif return ServiceResult.Good; } /// /// Generates a new value each time the value is read. /// 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); } } } }