/* ======================================================================== * 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.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Opc.Ua.Client; namespace Opc.Ua.WotCon.Client { /// /// Thin wrapper around an individual WoT asset object plus its /// associated WoTAssetFileType child. Composes the generated /// proxy and exposes the /// upload / download / browse helpers needed to drive the /// OPC 10100-1 ยง6.2 workflow from the client side. /// public sealed class WotAssetClient { internal WotAssetClient( ISession session, NodeId assetId, string name, WoTAssetFileTypeClient file, ITelemetryContext telemetry) { Session = session; AssetId = assetId; Name = name; File = file; Telemetry = telemetry; } /// /// The OPC UA session. /// public ISession Session { get; } /// /// Asset NodeId. /// public NodeId AssetId { get; } /// /// Asset display name (BrowseName minus namespace prefix). /// public string Name { get; } /// The generated WoTAssetFileType proxy. public WoTAssetFileTypeClient File { get; } /// /// Telemetry context. /// public ITelemetryContext Telemetry { get; } /// /// Uploads a WoT Thing Description and triggers /// CloseAndUpdate. After this call the server has /// materialised the asset's variables and methods. /// public ValueTask UploadThingDescriptionAsync( ReadOnlyMemory thingDescriptionJson, CancellationToken ct = default) { return File.UploadAndUpdateAsync(thingDescriptionJson, ct: ct); } /// /// Uploads a WoT Thing Description streamed from /// and triggers /// CloseAndUpdate. Streams are read sequentially until /// end-of-stream; non-seekable streams (e.g. network or /// compressed streams) are supported. The caller retains /// ownership of and is /// responsible for disposing it. /// public ValueTask UploadThingDescriptionAsync( Stream thingDescriptionJson, CancellationToken ct = default) { return File.UploadAndUpdateAsync(thingDescriptionJson, ct: ct); } /// /// Downloads the currently persisted WoT Thing Description. /// public ValueTask DownloadThingDescriptionAsync( CancellationToken ct = default) { return File.DownloadAllAsync(ct: ct); } /// /// Downloads the currently persisted WoT Thing Description and /// writes it sequentially into . /// The caller retains ownership of /// and is responsible for /// disposing it. /// public ValueTask DownloadThingDescriptionAsync( Stream destination, CancellationToken ct = default) { return File.DownloadToAsync(destination, ct: ct); } /// /// Enumerates the asset's property variables (children attached /// via HasWoTComponent). /// public IAsyncEnumerable EnumeratePropertiesAsync( CancellationToken ct = default) { return BrowseChildrenAsync( ReferenceTypeIds.HasWoTComponent, includeSubtypes: false, nodeClasses: NodeClass.Variable, ct); } /// /// Enumerates the asset's action methods (children attached via /// HasComponent). /// public IAsyncEnumerable EnumerateActionsAsync( CancellationToken ct = default) { return BrowseChildrenAsync( Ua.ReferenceTypeIds.HasComponent, includeSubtypes: false, nodeClasses: NodeClass.Method, ct); } private async IAsyncEnumerable BrowseChildrenAsync( ExpandedNodeId referenceType, bool includeSubtypes, NodeClass nodeClasses, [EnumeratorCancellation] CancellationToken ct) { var refTypeId = ExpandedNodeId.ToNodeId(referenceType, Session.NamespaceUris); (_, _, ArrayOf references) = await Session.BrowseAsync( requestHeader: null, view: null, AssetId, maxResultsToReturn: 0, BrowseDirection.Forward, refTypeId, includeSubtypes: includeSubtypes, (uint)nodeClasses, ct).ConfigureAwait(false); var snapshot = new ReferenceDescription[references.Count]; for (int i = 0; i < references.Count; i++) { snapshot[i] = references[i]; } foreach (ReferenceDescription reference in snapshot) { ct.ThrowIfCancellationRequested(); var targetId = ExpandedNodeId.ToNodeId(reference.NodeId, Session.NamespaceUris); yield return new WotAssetVariableEntry( targetId, reference.BrowseName.Name ?? string.Empty); } } } /// /// A property / action child of an asset surfaced by /// / /// . /// public sealed record WotAssetVariableEntry(NodeId NodeId, string BrowseName); }