/* ========================================================================
* 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);
}
}
}
}