/* ======================================================================== * Copyright (c) 2005-2026 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.Threading; using System.Threading.Tasks; using Opc.Ua; using Opc.Ua.AMB; using Opc.Ua.AMB.Server; using Opc.Ua.AMB.Server.Assets; using Opc.Ua.AMB.Server.Health; using Opc.Ua.Di; using Opc.Ua.Di.Server.Builders; using Opc.Ua.Di.Server.Hosting; namespace AmbSample { /// /// The assets of the sample plant: a cooling pump and the flow sensor it /// relies on, both Device Integration devices, and a hydraulic press /// built by . /// internal sealed class Plant { /// The hierarchical location of the assets. public const string Line = "Plant1/Hall3/Line2"; /// The operational location of the cooling circuit. public const string Cooling = "Utilities/CoolingCircuit"; /// The IRDI of an ECLASS class of centrifugal pumps, for the classification. public const string CentrifugalPumpIrdi = "0173-1#01-AKE798#019"; /// /// The dictionary entry of the class (OPC 10000-19), which the AMB node /// manager defines below Server/Dictionaries. /// public static readonly ExpandedNodeId CentrifugalPump = new( CentrifugalPumpIrdi, AmbServerOptions.IrdiNamespaceUri); /// Gets the cooling pump, once registered. public IAssetHandle? Pump { get; private set; } /// Gets the flow sensor, once registered. public IAssetHandle? Sensor { get; private set; } /// Gets the press, once registered. public IAssetHandle? Press { get; set; } /// /// Creates the devices and registers them as assets. Runs while the /// Machinery node manager builds its address space, which is when /// health alarms and maintenance activities can be created. /// public async ValueTask ConfigureDevicesAsync(IDiPostSetupContext context) { IAssetManagement assets = context.GetRequiredService(); CancellationToken ct = context.CancellationToken; ushort ns = context.Manager.InstanceNamespaceIndex; IDeviceBuilder sensor = await context .CreateDeviceAsync(new QualifiedName("FlowSensor", ns)) .ConfigureAwait(false); sensor.WithIdentification(identification => { identification.Manufacturer = new LocalizedText("en", "Acme Sensors"); identification.Model = new LocalizedText("en", "FlowMag 20"); identification.SerialNumber = "FM20-1187"; identification.ProductInstanceUri = "urn:acme-sensors:flowmag20:FM20-1187"; identification.HardwareRevision = "C"; identification.SoftwareRevision = "1.8.0"; identification.RevisionCounter = 3; }); Sensor = await sensor.RegisterAsAssetAsync( assets, asset => asset .WithConfigurableAssetId() .WithDeviceHealth() .WithHealthAlarm("SelfTest", AssetHealthAlarmKind.CheckFunction, AmbConditionClass.SelfTestFailure) .WithMaintenance( "Calibration", AmbConditionClass.CalibrationDue, details => { details.Description = new LocalizedText("en", "Calibrate the flow sensor."); details.PlannedDate = DateTime.UtcNow.AddDays(30); details.EstimatedDowntime = TimeSpan.FromMinutes(30); }) .LocatedIn(AssetLocationKind.Operational, Cooling) .WithLocation(AssetLocationKind.Operational, Cooling), ct).ConfigureAwait(false); IDeviceBuilder pump = await context .CreateDeviceAsync(new QualifiedName("CoolingPump", ns)) .ConfigureAwait(false); pump.WithIdentification(identification => { identification.Manufacturer = new LocalizedText("en", "Acme Pumps"); identification.Model = new LocalizedText("en", "CW-80"); identification.SerialNumber = "CW80-0042"; identification.ProductInstanceUri = "urn:acme-pumps:cw80:CW80-0042"; }); Pump = await pump.RegisterAsAssetAsync( assets, asset => asset .WithConfigurableAssetId("CP-01") .WithDeviceHealth(deriveFromAlarms: true) .WithHealthAlarm( "BearingTemperature", AssetHealthAlarmKind.OffSpec, AmbConditionClass.OverTemperature) .WithHealthAlarm("Fieldbus", AssetHealthAlarmKind.Failure, AmbConditionClass.ConnectionFailure) .WithMaintenance( "AnnualInspection", AmbConditionClass.Inspection, details => { details.Description = new LocalizedText("en", "Annual inspection of the cooling pump."); details.PlannedDate = DateTime.UtcNow.AddMinutes(2); details.EstimatedDowntime = TimeSpan.FromHours(2); details.MaintenanceSupplier = new NameNodeIdDataType { Name = new LocalizedText("en", "Acme Service"), NodeId = NodeId.Null }; details.MaintenanceMethod = MaintenanceMethodEnum.Local; }) .WithDocumentationLinks(links => links .Add("OperatingManual", "https://acme-pumps.example/cw80/manual.pdf") .Add("SpareParts", "https://acme-pumps.example/cw80/spares") .AddEditable("OperatorHandbook") .AllowUserLinks()) .WithVersionInformation("B", "2.4.1", 1) .LocatedIn(AssetLocationKind.Hierarchical, Line) .LocatedIn(AssetLocationKind.Operational, Cooling) .WithLocation(AssetLocationKind.Hierarchical, Line, writable: true) .WithLocalTime(60, writable: true) .ClassifiedAs(CentrifugalPump) .WithRequirements(requirements => requirements .Add("SupplyVoltage", Variant.From(400.0)) .Add("CoolingWaterTemperature", Variant.From(25.0))) .WithCapabilities(capabilities => capabilities .Add("RatedFlow", Variant.From(25.0)) .Add("Medium", Variant.From("Water"))) .RelatesTo(Opc.Ua.ReferenceTypeIds.Utilizes, Sensor.NodeId), ct).ConfigureAwait(false); } } }