using ZB.MOM.WW.OtOpcUa.Commons.Types; namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa; /// /// v3 dual-namespace NodeId + namespace-URI authority. Two OPC UA namespaces expose the same /// underlying values under two identity schemes: /// /// Raw (): the device-oriented tree /// Folder→Driver→Device→TagGroup→Tag. A node's s= identifier IS its /// RawPath — folders/drivers/devices/groups included (each keys /// on its own RawPath prefix). /// Uns (): the equipment-oriented tree /// Area→Line→Equipment→signal. A node's s= identifier is the slash-joined /// Area/Line/Equipment[/EffectiveName]. /// /// These replace the single https://zb.com/otopcua/ns namespace and the retired /// EquipmentNodeIds ({equipmentId}/{folderPath}/{name}) scheme. Realm travels alongside /// the identifier as an at every sink seam — the namespace is /// never parsed back out of the id string. Both schemes share /// and its ordinal, case-sensitive segment charset so identity is enforced in one place. /// public static class V3NodeIds { /// The Raw namespace URI (device-oriented subtree). public const string RawNamespaceUri = "https://zb.com/otopcua/raw"; /// The UNS namespace URI (equipment-oriented subtree). public const string UnsNamespaceUri = "https://zb.com/otopcua/uns"; /// The namespace URI for a realm. /// The address-space realm. /// The realm's namespace URI. /// Unknown realm. public static string NamespaceUri(AddressSpaceRealm realm) => realm switch { AddressSpaceRealm.Raw => RawNamespaceUri, AddressSpaceRealm.Uns => UnsNamespaceUri, _ => throw new ArgumentOutOfRangeException(nameof(realm), realm, "Unknown address-space realm."), }; // ----- Raw realm ----- /// /// The Raw-namespace s= identifier for a raw node — identical to its /// RawPath (folders, drivers, devices, groups, and tags all key /// on their own RawPath). A pass-through seam so call sites read intent rather than a bare /// string; the argument is expected to already be a RawPaths-built path. /// /// The (already-built) RawPath. /// The Raw-namespace s= identifier (== ). public static string Raw(string rawPath) { ArgumentException.ThrowIfNullOrEmpty(rawPath); return rawPath; } // ----- Uns realm ----- /// /// The UNS-namespace s= identifier for a folder or variable, slash-joined from /// ordered segments (Area, then Line, then Equipment, then an optional EffectiveName). /// Every segment is validated via (no embedded /// separator, no leading/trailing whitespace) so a UNS id round-trips like a RawPath. /// /// The ordered, non-empty segments (Area → leaf). /// The slash-joined UNS s= identifier. /// A segment is invalid, or the sequence is empty. public static string Uns(params string[] segments) => UnsFromSegments(segments); /// Build a UNS s= identifier from ordered segments. See . /// The ordered, non-empty segments (Area → leaf). /// The slash-joined UNS s= identifier. public static string Uns(IEnumerable segments) => UnsFromSegments(segments); /// /// Append a child segment (a Line under an Area, an Equipment under a Line, an /// EffectiveName under an Equipment) to an already-built UNS path. /// /// The parent UNS path (assumed already valid). /// The child segment to append. /// The combined UNS s= identifier. /// The child segment is invalid, or the parent is blank. public static string UnsChild(string parentUnsPath, string childSegment) { ArgumentException.ThrowIfNullOrEmpty(parentUnsPath); var error = RawPaths.ValidateSegment(childSegment); if (error is not null) throw new ArgumentException($"Invalid UNS NodeId segment ('{childSegment}'): {error}", nameof(childSegment)); return parentUnsPath + RawPaths.Separator + childSegment; } private static string UnsFromSegments(IEnumerable segments) { ArgumentNullException.ThrowIfNull(segments); var list = segments as IReadOnlyList ?? segments.ToList(); if (list.Count == 0) throw new ArgumentException("A UNS NodeId must have at least one segment.", nameof(segments)); for (var i = 0; i < list.Count; i++) { var error = RawPaths.ValidateSegment(list[i]); if (error is not null) throw new ArgumentException($"Invalid UNS NodeId segment at index {i} ('{list[i]}'): {error}", nameof(segments)); } return string.Join(RawPaths.Separator, list); } }