/* ======================================================================== * 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; namespace Opc.Ua.Server.Fluent { /// /// Extension methods that set the EngineeringUnits and /// EURange properties on a /// variable resolved through the fluent builder. Fail-fast with /// when the resolved node /// is not a (the only OPC UA node /// class that declares EngineeringUnits / EURange /// child properties). /// /// /// /// The extensions create the property child if it does not exist /// yet (using the BaseAnalogState.AddEngineeringUnits / /// AddEURange helpers emitted by the source generator), and /// then set its attribute to /// the supplied value. They are AOT-safe — no reflection. /// /// /// Typical usage: /// /// builder.Variable<double>("Pump #1/Operational/Measurements/FluidTemperature") /// .WithEngineeringUnits(EUInformations.Kelvin) /// .WithEURange(min: 233.15, max: 473.15) /// .OnRead(SimulateTemperature); /// /// /// public static class EngineeringUnitsBuilderExtensions { /// /// Sets the EngineeringUnits property of the resolved /// variable to . /// /// CLR value type of the variable. /// The variable builder. /// /// Engineering units descriptor (unit name, namespace, optional /// IEC 80000 code). Must not be . /// /// The same builder, for chaining. /// /// Thrown when or /// is . /// /// /// when the resolved /// node is not a . /// public static IVariableBuilder WithEngineeringUnits( this IVariableBuilder builder, EUInformation units) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } BaseAnalogState analog = RequireAnalog(builder); EnsureEngineeringUnitsProperty(analog, builder.Builder.Context); analog.EngineeringUnits!.Value = units ?? throw new ArgumentNullException(nameof(units)); return builder; } /// /// Sets the EURange property of the resolved variable to /// new Range(high: , low: ). /// /// CLR value type of the variable. /// The variable builder. /// Inclusive lower bound. /// Inclusive upper bound. /// The same builder, for chaining. /// /// Thrown when is . /// /// /// Thrown when > . /// /// /// when the resolved /// node is not a . /// public static IVariableBuilder WithEURange( this IVariableBuilder builder, double min, double max) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (min > max) { throw new ArgumentException( "min must be less than or equal to max.", nameof(min)); } BaseAnalogState analog = RequireAnalog(builder); EnsureEURangeProperty(analog, builder.Builder.Context); analog.EURange!.Value = new Range(high: max, low: min); return builder; } /// /// Convenience overload that sets EngineeringUnits and /// EURange in one call. /// /// Variable value type. public static IVariableBuilder WithUnits( this IVariableBuilder builder, EUInformation units, double min, double max) { return builder .WithEngineeringUnits(units) .WithEURange(min, max); } private static BaseAnalogState RequireAnalog( IVariableBuilder builder) { if (builder.Node is not BaseAnalogState analog) { throw ServiceResultException.Create( StatusCodes.BadTypeMismatch, "Cannot set engineering units on '{0}': resolved node is '{1}', " + "not a BaseAnalogState (or subtype). Only AnalogItemType / " + "BaseAnalogType variables carry EngineeringUnits / EURange properties.", builder.Node.BrowseName, builder.Node.GetType().Name); } return analog; } private static void EnsureEngineeringUnitsProperty( BaseAnalogState analog, ISystemContext context) { if (analog.EngineeringUnits == null) { analog.AddEngineeringUnits(context); } } private static void EnsureEURangeProperty( BaseAnalogState analog, ISystemContext context) { if (analog.EURange == null) { analog.AddEURange(context); } } } }