From 362cd0851580ba180d20265f04d2d7006ab14375 Mon Sep 17 00:00:00 2001 From: Kazeem Oladipupo <67549739+Kaz040@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:06:36 +0200 Subject: [PATCH 01/15] add bacnet project --- .../AidBacnetConnection.cs | 13 ++++ .../AidInterfaceStatus.cs | 14 +++- .../AssetInterfaceOptions.cs | 1 + .../MappingsAssetInterfacesDescription.cs | 66 +++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs new file mode 100644 index 00000000..d5ec0b65 --- /dev/null +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -0,0 +1,13 @@ +using AasxPluginAssetInterfaceDescription; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AasxPluginAssetInterfaceDescription +{ + public class AidBacnetConnection : AidBaseConnection + { + } +} diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index d492efaa..2f200c37 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -29,6 +29,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.IO; using Newtonsoft.Json.Linq; using Newtonsoft.Json; +using AasxPluginAssetInterfaceDescription; namespace AasxPluginAssetInterfaceDescription { @@ -97,7 +98,7 @@ public class AidIfxItemStatus public AnyUiUIElement RenderedUiElement = null; } - public enum AidInterfaceTechnology { HTTP, Modbus, MQTT, OPCUA } + public enum AidInterfaceTechnology { HTTP, Modbus, MQTT, OPCUA, BACNET } public class AidInterfaceStatus { @@ -451,7 +452,7 @@ public class AidAllInterfaceStatus /// /// Current setting, which technologies shall be used. /// - public bool[] UseTech = { false, false, false, true }; + public bool[] UseTech = { false, false, false, true, false }; /// /// Will hold connections steady and continously update values, either by @@ -473,6 +474,9 @@ public class AidAllInterfaceStatus public AidGenericConnections OpcUaConnections = new AidGenericConnections(); + public AidGenericConnections BacnetConnections = + new AidGenericConnections(); + public AidAllInterfaceStatus(LogInstance log = null) { _log = log; @@ -503,6 +507,7 @@ public void RememberAidSubmodel(Aas.ISubmodel sm, AssetInterfaceOptionsRecord op UseTech[(int)AidInterfaceTechnology.Modbus] = optRec.UseModbus; UseTech[(int)AidInterfaceTechnology.MQTT] = optRec.UseMqtt; UseTech[(int)AidInterfaceTechnology.OPCUA] = optRec.UseOpcUa; + UseTech[(int)AidInterfaceTechnology.BACNET] = optRec.UseBacnet; } } @@ -539,6 +544,10 @@ protected AidBaseConnection GetOrCreate( case AidInterfaceTechnology.OPCUA: conn = OpcUaConnections.GetOrCreate(endpointBase, log); break; + + case AidInterfaceTechnology.BACNET: + conn = BacnetConnections.GetOrCreate(endpointBase, log); + break; } conn.UpdateFreqMs = ifcStatus.UpdateFreqMs; @@ -821,6 +830,7 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = if (tech == AidInterfaceTechnology.Modbus) ifxs = dataAid?.InterfaceMODBUS; if (tech == AidInterfaceTechnology.MQTT) ifxs = dataAid?.InterfaceMQTT; if (tech == AidInterfaceTechnology.OPCUA) ifxs = dataAid?.InterfaceOPCUA; + if (tech == AidInterfaceTechnology.BACNET) ifxs = dataAid?.InterfaceBACNET; if (ifxs == null || ifxs.Count < 1) continue; foreach (var ifx in ifxs) diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs index f9dff130..2cd204d5 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs @@ -29,6 +29,7 @@ public class AssetInterfaceOptionsRecord : AasxPluginOptionsLookupRecordBase public bool UseModbus = true; public bool UseMqtt = true; public bool UseOpcUa = true; + public bool UseBacnet = true; } public class AssetInterfaceOptions : AasxPluginLookupOptionsBase diff --git a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs index b36edc76..7837e85b 100644 --- a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs @@ -371,6 +371,9 @@ public class CD_PropertyName [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#properties", Card = AasxPredefinedCardinality.ZeroToOne)] public CD_Properties Properties = null; + [AasConcept(Cd = "https://www.w3.org/2019/wot/td#hasUriTemplateSchema", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Properties UriVariables = null; + [AasConcept(Cd = "https://www.w3.org/2019/wot/td#hasForm", Card = AasxPredefinedCardinality.One)] public CD_Forms Forms = new CD_Forms(); @@ -476,6 +479,12 @@ public class CD_Forms [AasConcept(Cd = "https://www.w3.org/2019/wot/mqtt#hasQoSFlag", Card = AasxPredefinedCardinality.ZeroToOne)] public string Mqv_qos; + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#usesService", Card = AasxPredefinedCardinality.ZeroToOne)] + public string bacv_useService; + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Bacv_hasDataType bacv_hasDataType = null; + [AasConcept(Cd = "https://www.w3.org/2019/wot/opc-ua#pollingTime", Card = AasxPredefinedCardinality.ZeroToOne)] public string OpcUa_pollingTime; @@ -502,6 +511,59 @@ public class CD_Htv_headers public AasClassMapperInfo __Info__ = null; } + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType")] + public class CD_Bacv_hasDataType + { + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasNamedMember", Card = AasxPredefinedCardinality.ZeroToOne)] + public List bacv_hasNamedMember = new List(); + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasValueMap ", Card = AasxPredefinedCardinality.ZeroToOne)] + public List bacv_hasValueMap = new List(); + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#isIso8601", Card = AasxPredefinedCardinality.ZeroToOne)] + public string bacv_isISO8601; + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasBinaryRepresentation", Card = AasxPredefinedCardinality.ZeroToOne)] + public string bacv_hasBinaryRepresentation; + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasMember", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Bacv_hasDataType bacv_hasMember = null; + + // auto-generated informations + public AasClassMapperInfo __Info__ = null; + } + + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasNamedMember")] + public class CD_Bacv_hasNamedMember + { + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Bacv_hasDataType bacv_hasDataType = null; + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasfieldName", Card = AasxPredefinedCardinality.One)] + public string bacv_hasFieldName; + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasContextTag", Card = AasxPredefinedCardinality.One)] + public string bacv_hasContextTag; + + // auto-generated informations + public AasClassMapperInfo __Info__ = null; + } + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasValueMap")] + public class CD_Bacv_hasValueMap + { + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasLogicalVal", Card = AasxPredefinedCardinality.One)] + public string bacv_hasLogicalVal; + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasProtocolVal", Card = AasxPredefinedCardinality.One)] + public string bacv_hasProtocolVal; + + // auto-generated informations + public AasClassMapperInfo __Info__ = null; + } + [AasConcept(Cd = "https://www.w3.org/2019/wot/td#ActionAffordance")] public class CD_Actions { @@ -545,6 +607,10 @@ public class CD_AssetInterfacesDescription SupplSemId = "http://www.w3.org/2011/opc-ua")] public List InterfaceOPCUA = new List(); + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface", Card = AasxPredefinedCardinality.ZeroToMany, + SupplSemId = "http://www.w3.org/2022/bacnet")] + public List InterfaceBACNET = new List(); + // auto-generated informations public AasClassMapperInfo __Info__ = null; } From 4237f7a41bd9763af84ff55345d5eab0358bf2df Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Mon, 2 Jun 2025 17:22:40 +0200 Subject: [PATCH 02/15] Add Bacnet logo --- .../AasxPluginAssetInterfaceDesc.csproj | 2 ++ .../Resources/logo-bacnet.png | Bin 0 -> 31735 bytes 2 files changed, 2 insertions(+) create mode 100644 src/AasxPluginAssetInterfaceDesc/Resources/logo-bacnet.png diff --git a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj index 6166ed62..0f71db40 100644 --- a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj +++ b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj @@ -15,6 +15,7 @@ + @@ -33,6 +34,7 @@ + diff --git a/src/AasxPluginAssetInterfaceDesc/Resources/logo-bacnet.png b/src/AasxPluginAssetInterfaceDesc/Resources/logo-bacnet.png new file mode 100644 index 0000000000000000000000000000000000000000..dff86fc9aedcee92f5c1a526a1bc64545d6d8b3a GIT binary patch literal 31735 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 E1{}(K6aWAK literal 0 HcmV?d00001 From af60787898e2613bc2802186a0b410d346d0e2ae Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Mon, 2 Jun 2025 18:05:15 +0200 Subject: [PATCH 03/15] Update BACnet logo --- .../Resources/logo-bacnet.png | Bin 31735 -> 5712 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/Resources/logo-bacnet.png b/src/AasxPluginAssetInterfaceDesc/Resources/logo-bacnet.png index dff86fc9aedcee92f5c1a526a1bc64545d6d8b3a..7a59b61c1afc5e7eb6a13b6b6c7905545c899bb9 100644 GIT binary patch literal 5712 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 E1{}(K6aWAK From 89703aca6dc7f3dacf6e8297b32ade915d211c81 Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Tue, 3 Jun 2025 10:36:31 +0200 Subject: [PATCH 04/15] Edit MappingAssetInterfacesDescription --- .../AssetInterfaceAnyUiControl.cs | 4 ++++ .../MappingsAssetInterfacesDescription.cs | 21 +++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index a9678dc2..e1442f9e 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -125,6 +125,10 @@ public void Start( AnyUiGdiHelper.CreateAnyUiBitmapFromResource( "AasxPluginAssetInterfaceDesc.Resources.logo-opc-ua.png", assembly: Assembly.GetExecutingAssembly())); + _dictTechnologyToBitmap.Add(AidInterfaceTechnology.BACNET, + AnyUiGdiHelper.CreateAnyUiBitmapFromResource( + "AasxPluginAssetInterfaceDesc.Resources.logo-bacnet.png", + assembly: Assembly.GetExecutingAssembly())); } // fill given panel diff --git a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs index 7837e85b..62df64c5 100644 --- a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs @@ -514,12 +514,6 @@ public class CD_Htv_headers [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType")] public class CD_Bacv_hasDataType { - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasNamedMember", Card = AasxPredefinedCardinality.ZeroToOne)] - public List bacv_hasNamedMember = new List(); - - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasValueMap ", Card = AasxPredefinedCardinality.ZeroToOne)] - public List bacv_hasValueMap = new List(); - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#isIso8601", Card = AasxPredefinedCardinality.ZeroToOne)] public string bacv_isISO8601; @@ -529,23 +523,28 @@ public class CD_Bacv_hasDataType [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasMember", Card = AasxPredefinedCardinality.ZeroToOne)] public CD_Bacv_hasDataType bacv_hasMember = null; + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasNamedMember", Card = AasxPredefinedCardinality.ZeroToOne)] + public List bacv_hasNamedMember = new List(); + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasValueMap ", Card = AasxPredefinedCardinality.ZeroToOne)] + public List bacv_hasValueMap = new List(); + // auto-generated informations public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasNamedMember")] public class CD_Bacv_hasNamedMember { - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType", Card = AasxPredefinedCardinality.ZeroToOne)] - public CD_Bacv_hasDataType bacv_hasDataType = null; - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasfieldName", Card = AasxPredefinedCardinality.One)] public string bacv_hasFieldName; - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasContextTag", Card = AasxPredefinedCardinality.One)] + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasContextTag", Card = AasxPredefinedCardinality.ZeroToOne)] public string bacv_hasContextTag; + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Bacv_hasDataType bacv_hasDataType = null; + // auto-generated informations public AasClassMapperInfo __Info__ = null; } From 8141e808c4a90c0e76e52ffc76b767a6c14280ca Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Fri, 6 Jun 2025 00:32:16 +0200 Subject: [PATCH 05/15] Changes --- .../AasxPluginAssetInterfaceDesc.csproj | 1 + .../AidBacnetConnection.cs | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj index 0f71db40..02599e40 100644 --- a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj +++ b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj @@ -44,6 +44,7 @@ + diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs index d5ec0b65..b2ea82aa 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -4,10 +4,33 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using AasxPredefinedConcepts; +using Aas = AasCore.Aas3_0; +using AdminShellNS; +using Extensions; +using AasxIntegrationBase; +using AasxPredefinedConcepts.AssetInterfacesDescription; +using FluentModbus; +using System.Net; +using System.Text.RegularExpressions; +using System.Globalization; +using System.Net.Http; +using AdminShellNS.DiaryData; +using AasxIntegrationBase.AdminShellEvents; +using System.Drawing; +using MQTTnet; +using MQTTnet.Client; +using System.Web.Services.Description; +using AasxOpcUa2Client; +using System.IO.BACnet; +using System.Threading; +using System.Diagnostics; namespace AasxPluginAssetInterfaceDescription { public class AidBacnetConnection : AidBaseConnection { - } + + } } + \ No newline at end of file From 0c94ec2efa108ba60548aee0f8be855f29c12f3f Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Sun, 8 Jun 2025 17:14:19 +0200 Subject: [PATCH 06/15] Update BACnet Plugin --- .../AidBacnetConnection.cs | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs index b2ea82aa..a727d328 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -30,7 +30,92 @@ namespace AasxPluginAssetInterfaceDescription { public class AidBacnetConnection : AidBaseConnection { + public BacnetClient Client; - } + override public async Task Open() + { + try + { + // Simple initialization + Client = new BacnetClient(); + + // Parse device ID from URI (bacnet://1234) + //string deviceIdStr = TargetUri.Host; + //if (!uint.TryParse(deviceIdStr, out DeviceId)) + // return false; + + // Set timeout if specified + if (TimeOutMs >= 10) + Client.Timeout = (int)TimeOutMs; + + // Start the client + Client.Start(); + + Client.OnIam += new BacnetClient.IamHandler(handler_OnIam); + + Client.WhoIs(); + Thread.Sleep(1000); // Allow time for responses + + Console.WriteLine("Devices discovered:"); + lock (DevicesList) + { + foreach (var device in DevicesList) + { + Console.WriteLine($"Device ID: {device.device_id}, Address: {device.adr}"); + } + } + + await Task.Yield(); + return true; + } + catch + { + Client = null; + return false; + } + } + + override public bool IsConnected() + { + // nothing to do, this simple http connection is stateless + return Client != null; + } + + override public void Close() + { + // nothing to do, this simple http connection is stateless + } + + override public async Task UpdateItemValueAsync(AidIfxItemStatus item) + { + // access + if (item?.FormData?.Href?.HasContent() != true + || item.FormData.Modv_function?.HasContent() != true + || !IsConnected()) + return 0; + int res = 0; + + // Decode address and value + var match = Regex.Match(item.FormData.Href, @"^(\d+)(\?value=(\d+))?$"); + if (!match.Success) + return 0; + if (!uint.TryParse(match.Groups[1].ToString(), out var address)) + return 0; + if (match.Groups[3].Success && !uint.TryParse(match.Groups[3].ToString(), out var value)) + return 0; + // Perform the write operation + try + { + Client.WriteProperty(address, BacnetPropertyIds.PROP_PRESENT_VALUE, value); + res = 1; // Success + } + catch (Exception ex) + { + Debug.WriteLine($"Bacnet write error: {ex.Message}"); + res = -1; // Error + } + await Task.Yield(); + return res; + } } \ No newline at end of file From 4761fcd996324eb8b4debfb05c7e9d98abc9398a Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Tue, 10 Jun 2025 09:30:39 +0200 Subject: [PATCH 07/15] Bacnet Implemntation Chnages --- .../AasxPluginAssetInterfaceDesc.options.json | 3 +- .../AidBacnetConnection.cs | 79 ++++++++++++------- .../AidInterfaceStatus.cs | 32 ++++---- .../AssetInterfaceAnyUiControl.cs | 26 +++--- .../AssetInterfaceOptions.cs | 4 +- .../MappingsAssetInterfacesDescription.cs | 30 +++---- 6 files changed, 100 insertions(+), 74 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json index 15734b7b..1d653b26 100644 --- a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json +++ b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json @@ -12,7 +12,8 @@ "UseHttp": false, "UseModbus": false, "UseMqtt": false, - "UseOpcUa": true + "UseOpcUa": true, + "UseBacnet": true }, { "IsDescription": false, diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs index a727d328..c98dbf85 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -31,7 +31,20 @@ namespace AasxPluginAssetInterfaceDescription public class AidBacnetConnection : AidBaseConnection { public BacnetClient Client; + static List DevicesList = new List(); + static void handler_OnIam(BacnetClient sender, BacnetAddress adr, uint device_id, uint max_apdu, BacnetSegmentations segmentation, ushort vendor_id) + { + lock (DevicesList) + { + // Device already registered? + foreach (BacNode bn in DevicesList) + if (bn.getAdd(device_id) != null) return; // Yes + + // Not already in the list + DevicesList.Add(new BacNode(adr, device_id)); // Add it + } // Closing brace added here + } // Closing brace added here override public async Task Open() { try @@ -42,7 +55,7 @@ override public async Task Open() // Parse device ID from URI (bacnet://1234) //string deviceIdStr = TargetUri.Host; //if (!uint.TryParse(deviceIdStr, out DeviceId)) - // return false; + //return false; // Set timeout if specified if (TimeOutMs >= 10) @@ -86,36 +99,48 @@ override public void Close() // nothing to do, this simple http connection is stateless } - override public async Task UpdateItemValueAsync(AidIfxItemStatus item) - { - // access - if (item?.FormData?.Href?.HasContent() != true - || item.FormData.Modv_function?.HasContent() != true - || !IsConnected()) - return 0; - int res = 0; - - // Decode address and value - var match = Regex.Match(item.FormData.Href, @"^(\d+)(\?value=(\d+))?$"); - if (!match.Success) - return 0; - if (!uint.TryParse(match.Groups[1].ToString(), out var address)) - return 0; - if (match.Groups[3].Success && !uint.TryParse(match.Groups[3].ToString(), out var value)) - return 0; - // Perform the write operation + override public int UpdateItemValue(AidIfxItemStatus item) + { try { - Client.WriteProperty(address, BacnetPropertyIds.PROP_PRESENT_VALUE, value); - res = 1; // Success + // Example logic to update item value + if (item != null && !string.IsNullOrEmpty(item.Location)) + { + // Simulate updating the value + item.Value = "UpdatedValue"; + return 0; // Return success code + } + else + { + return -1; // Return error code for invalid item + } } catch (Exception ex) { - Debug.WriteLine($"Bacnet write error: {ex.Message}"); - res = -1; // Error + // Log the exception if necessary + Console.WriteLine($"Error updating item value: {ex.Message}"); + return -2; // Return error code for exception + } + } + + public class BacNode + { + public BacnetAddress adr; + public uint device_id; + + public BacNode(BacnetAddress adr, uint device_id) + { + this.adr = adr; + this.device_id = device_id; + } + + public BacnetAddress getAdd(uint device_id) + { + if (this.device_id == device_id) + return adr; + else + return null; } - await Task.Yield(); - return res; } -} - \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index 2f200c37..b4f6e687 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -29,7 +29,6 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.IO; using Newtonsoft.Json.Linq; using Newtonsoft.Json; -using AasxPluginAssetInterfaceDescription; namespace AasxPluginAssetInterfaceDescription { @@ -121,7 +120,7 @@ public class AidInterfaceStatus /// /// The information items (properties, actions, events) /// - public MultiValueDictionary Items = + public MultiValueDictionary Items = new MultiValueDictionary(); /// @@ -202,7 +201,7 @@ protected string ComputeKey(string key) return key; } - public void SetLogLine (StoredPrint.Color color, string line) + public void SetLogLine(StoredPrint.Color color, string line) { LogColor = color; LogLine = line; @@ -220,7 +219,7 @@ public void AddItem(AidIfxItemStatus item) // compute key var key = ComputeKey(item?.FormData?.Href); - + // now add Items.Add(key, item); } @@ -402,7 +401,7 @@ public void NotifyOutputItems(AidIfxItemStatus item, string strval) } } - + } } } @@ -452,7 +451,7 @@ public class AidAllInterfaceStatus /// /// Current setting, which technologies shall be used. /// - public bool[] UseTech = { false, false, false, true, false }; + public bool[] UseTech = { false, false, false, true, true }; /// /// Will hold connections steady and continously update values, either by @@ -465,7 +464,7 @@ public class AidAllInterfaceStatus public AidGenericConnections HttpConnections = new AidGenericConnections(); - public AidGenericConnections ModbusConnections = + public AidGenericConnections ModbusConnections = new AidGenericConnections(); public AidGenericConnections MqttConnections = @@ -548,6 +547,7 @@ protected AidBaseConnection GetOrCreate( case AidInterfaceTechnology.BACNET: conn = BacnetConnections.GetOrCreate(endpointBase, log); break; + } conn.UpdateFreqMs = ifcStatus.UpdateFreqMs; @@ -771,17 +771,17 @@ public async Task UpdateValuesContinousByTickAsyc() // go thru all items (sync) foreach (var item in ifc.Items.Values) - ifc.ValueChanges += (UInt64) ifc.Connection.UpdateItemValue(item); + ifc.ValueChanges += (UInt64)ifc.Connection.UpdateItemValue(item); // go thru all items (async) // see: https://www.hanselman.com/blog/parallelforeachasync-in-net-6 await Parallel.ForEachAsync( - ifc.Items.Values, - new ParallelOptions() { MaxDegreeOfParallelism = 10 }, + ifc.Items.Values, + new ParallelOptions() { MaxDegreeOfParallelism = 10 }, async (item, token) => - { - ifc.ValueChanges += (UInt64) (await ifc.Connection.UpdateItemValueAsync(item)); - }); + { + ifc.ValueChanges += (UInt64)(await ifc.Connection.UpdateItemValueAsync(item)); + }); } } } @@ -863,7 +863,7 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = FormData = propName.Forms, Value = "???" }; - + // does (some) mapping have a source with this property name? var lst = new List(); @@ -884,7 +884,7 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = aidIfx.AddItem(ifcItem); ifcItem.MapOutputItems = lst; } - + // directly recurse? if (propName?.Properties?.Property != null) @@ -894,7 +894,7 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = child.Forms = propName.Forms; recurseProp(location + " . " + ifcItem.DisplayName, child); } - + }; if (ifx.InteractionMetadata?.Properties?.Property == null) diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index e1442f9e..ac3de740 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -46,7 +46,7 @@ public class AssetInterfaceAnyUiControl protected AnyUiSmallWidgetToolkit _uitk = new AnyUiSmallWidgetToolkit(); - protected Dictionary _dictTechnologyToBitmap = + protected Dictionary _dictTechnologyToBitmap = new Dictionary(); private System.Timers.Timer _dispatcherTimer = null; @@ -287,13 +287,13 @@ protected void RenderPanelInner( uitk.AddSmallLabelTo(grid, 0, 0, content: "Technologies:"); - var gridTech = uitk.AddSmallGridTo(grid, 0, 1, rows: 1, cols: 6, + var gridTech = uitk.AddSmallGridTo(grid, 0, 1, rows: 1, cols: 6, colWidths: new[] { "#", "#", "#", "#", "#", "#" }); foreach (var tech in AdminShellUtil.GetEnumValues()) { AnyUiUIElement.SetBoolFromControl( - uitk.AddSmallCheckBoxTo(gridTech, 0, 0 + ((int) tech), + uitk.AddSmallCheckBoxTo(gridTech, 0, 0 + ((int)tech), margin: new AnyUiThickness(0, 0, 10, 0), content: "" + tech.ToString(), isChecked: _allInterfaceStatus.UseTech[(int)tech], @@ -306,7 +306,7 @@ protected void RenderPanelInner( // uitk.AddSmallLabelTo(grid, 1, 0, content: "Startup:"); - + AnyUiUIElement.RegisterControl( uitk.AddSmallButtonTo(grid, 1, 1, margin: new AnyUiThickness(2), setHeight: 21, @@ -327,7 +327,7 @@ protected void RenderPanelInner( _allInterfaceStatus.PrepareAidInformation( _allInterfaceStatus.SmAidDescription, _allInterfaceStatus.SmAidMapping, - lambdaLookupReference: (rf) => package?.AasEnv?.FindReferableByReference(rf) ); + lambdaLookupReference: (rf) => package?.AasEnv?.FindReferableByReference(rf)); _allInterfaceStatus.SetAidInformationForUpdateAndTimeout(); // trigger a complete redraw, as the regions might emit @@ -349,7 +349,7 @@ protected void RenderPanelInner( setValueAsync: async (o) => { try - { + { // locked? if (_allInterfaceStatus?.ContinousRun == true) { @@ -455,7 +455,7 @@ public void Update(params object[] args) #region Interface items //===================== - + protected void RenderTripleRowData( AnyUiStackPanel view, AnyUiSmallWidgetToolkit uitk, List interfaces) @@ -465,7 +465,7 @@ protected void RenderTripleRowData( return; // ok - var grid = view.Add(uitk.AddSmallGrid(rows: interfaces.Count, cols: 5, + var grid = view.Add(uitk.AddSmallGrid(rows: interfaces.Count, cols: 5, colWidths: new[] { "40:", "1*", "1*", "1*", "180:" })); int rowIndex = 0; foreach (var ifx in interfaces) @@ -483,7 +483,7 @@ protected void RenderTripleRowData( colSpan: 5); if (_dictTechnologyToBitmap.ContainsKey(ifx.Technology)) - uitk.AddSmallImageTo(headGrid, 0, 0, + uitk.AddSmallImageTo(headGrid, 0, 0, margin: new AnyUiThickness(0, 4, 10, 4), bitmap: _dictTechnologyToBitmap[ifx.Technology]); @@ -507,13 +507,13 @@ protected void RenderTripleRowData( { // normal row, 5 bordered cells grid.RowDefinitions.Add(new AnyUiRowDefinition()); - var cols = new[] { + var cols = new[] { "Prop.", item.Location, item.DisplayName, "" + item.FormData?.Href, item.Value }; for (int ci = 0; ci < 5; ci++) { var brd = uitk.AddSmallBorderTo(grid, rowIndex, ci, - margin: (ci == 0) ? new AnyUiThickness(0, -1, 0, 0) - : new AnyUiThickness(-1, -1, 0, 0), + margin: (ci == 0) ? new AnyUiThickness(0, -1, 0, 0) + : new AnyUiThickness(-1, -1, 0, 0), borderThickness: new AnyUiThickness(1.0), borderBrush: AnyUiBrushes.DarkGray); brd.Child = new AnyUiSelectableTextBlock() { @@ -538,7 +538,7 @@ protected void RenderTripleRowData( var innerGrid = uitk.Set( uitk.AddSmallGridTo(grid, rowIndex++, 1, - rows: item.MapOutputItems.Count, + rows: item.MapOutputItems.Count, cols: 3, colWidths: new[] { "#", "*", "#" }, margin: new AnyUiThickness(2, 0, 0, 6)), colSpan: 4); diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs index 2cd204d5..425f5698 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs @@ -45,8 +45,8 @@ public static AssetInterfaceOptions CreateDefault() var rec1 = new AssetInterfaceOptionsRecord(); rec1.IsDescription = true; - rec1.AllowSubmodelSemanticId = new[] { - new Aas.Key(Aas.KeyTypes.Submodel, + rec1.AllowSubmodelSemanticId = new[] { + new Aas.Key(Aas.KeyTypes.Submodel, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel") }.ToList(); var rec2 = new AssetInterfaceOptionsRecord(); diff --git a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs index 62df64c5..d88ce3df 100644 --- a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs @@ -479,18 +479,18 @@ public class CD_Forms [AasConcept(Cd = "https://www.w3.org/2019/wot/mqtt#hasQoSFlag", Card = AasxPredefinedCardinality.ZeroToOne)] public string Mqv_qos; - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#usesService", Card = AasxPredefinedCardinality.ZeroToOne)] - public string bacv_useService; - - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType", Card = AasxPredefinedCardinality.ZeroToOne)] - public CD_Bacv_hasDataType bacv_hasDataType = null; - [AasConcept(Cd = "https://www.w3.org/2019/wot/opc-ua#pollingTime", Card = AasxPredefinedCardinality.ZeroToOne)] public string OpcUa_pollingTime; [AasConcept(Cd = "https://www.w3.org/2019/wot/opc-ua#timeout", Card = AasxPredefinedCardinality.ZeroToOne)] public string OpcUa_timeout; + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#usesService", Card = AasxPredefinedCardinality.ZeroToOne)] + public string Bacv_useService; + + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Bacv_hasDataType Bacv_hasDataType = null; + // auto-generated informations public AasClassMapperInfo __Info__ = null; } @@ -515,7 +515,7 @@ public class CD_Htv_headers public class CD_Bacv_hasDataType { [AasConcept(Cd = "http://www.w3.org/2022/bacnet#isIso8601", Card = AasxPredefinedCardinality.ZeroToOne)] - public string bacv_isISO8601; + public bool? bacv_isISO8601; [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasBinaryRepresentation", Card = AasxPredefinedCardinality.ZeroToOne)] public string bacv_hasBinaryRepresentation; @@ -524,23 +524,23 @@ public class CD_Bacv_hasDataType public CD_Bacv_hasDataType bacv_hasMember = null; [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasNamedMember", Card = AasxPredefinedCardinality.ZeroToOne)] - public List bacv_hasNamedMember = new List(); + public List bacv_hasNamedMember = null; - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasValueMap ", Card = AasxPredefinedCardinality.ZeroToOne)] - public List bacv_hasValueMap = new List(); + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasValueMap", Card = AasxPredefinedCardinality.ZeroToOne)] + public List bacv_hasValueMap = null; // auto-generated informations public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasNamedMember")] + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#NamedMember")] public class CD_Bacv_hasNamedMember { [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasfieldName", Card = AasxPredefinedCardinality.One)] public string bacv_hasFieldName; [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasContextTag", Card = AasxPredefinedCardinality.ZeroToOne)] - public string bacv_hasContextTag; + public bool? bacv_hasContextTag; [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasDataType", Card = AasxPredefinedCardinality.ZeroToOne)] public CD_Bacv_hasDataType bacv_hasDataType = null; @@ -549,7 +549,7 @@ public class CD_Bacv_hasNamedMember public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasValueMap")] + [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasMapEntry")] public class CD_Bacv_hasValueMap { @@ -557,7 +557,7 @@ public class CD_Bacv_hasValueMap public string bacv_hasLogicalVal; [AasConcept(Cd = "http://www.w3.org/2022/bacnet#hasProtocolVal", Card = AasxPredefinedCardinality.One)] - public string bacv_hasProtocolVal; + public int bacv_hasProtocolVal; // auto-generated informations public AasClassMapperInfo __Info__ = null; @@ -607,7 +607,7 @@ public class CD_AssetInterfacesDescription public List InterfaceOPCUA = new List(); [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface", Card = AasxPredefinedCardinality.ZeroToMany, - SupplSemId = "http://www.w3.org/2022/bacnet")] + SupplSemId = "http://www.w3.org/2022/bacnet")] public List InterfaceBACNET = new List(); // auto-generated informations From 41047de4f3a003bf6c62b3ba828590ae446f9f99 Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Wed, 11 Jun 2025 15:25:13 +0200 Subject: [PATCH 08/15] Bacnet Working Implimentation --- .../AidBacnetConnection.cs | 192 +++++++++--------- .../MappingsAssetInterfacesDescription.cs | 2 +- 2 files changed, 100 insertions(+), 94 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs index c98dbf85..4e844955 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -1,146 +1,152 @@ -using AasxPluginAssetInterfaceDescription; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using AasxIntegrationBase; +using AasxIntegrationBase.AdminShellEvents; +using AasxPluginAssetInterfaceDescription; using AasxPredefinedConcepts; -using Aas = AasCore.Aas3_0; +using AasxPredefinedConcepts.AssetInterfacesDescription; using AdminShellNS; +using AdminShellNS.DiaryData; using Extensions; -using AasxIntegrationBase; -using AasxPredefinedConcepts.AssetInterfacesDescription; using FluentModbus; -using System.Net; -using System.Text.RegularExpressions; -using System.Globalization; -using System.Net.Http; -using AdminShellNS.DiaryData; -using AasxIntegrationBase.AdminShellEvents; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; -using MQTTnet; -using MQTTnet.Client; -using System.Web.Services.Description; -using AasxOpcUa2Client; +using System.Globalization; using System.IO.BACnet; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; using System.Threading; -using System.Diagnostics; +using System.Threading.Tasks; +using System.Web.Services.Description; +using Aas = AasCore.Aas3_0; namespace AasxPluginAssetInterfaceDescription { public class AidBacnetConnection : AidBaseConnection { public BacnetClient Client; - static List DevicesList = new List(); - - static void handler_OnIam(BacnetClient sender, BacnetAddress adr, uint device_id, uint max_apdu, BacnetSegmentations segmentation, ushort vendor_id) - { - lock (DevicesList) - { - // Device already registered? - foreach (BacNode bn in DevicesList) - if (bn.getAdd(device_id) != null) return; // Yes - - // Not already in the list - DevicesList.Add(new BacNode(adr, device_id)); // Add it - } // Closing brace added here - } // Closing brace added here + private Dictionary DeviceAddresses = new Dictionary(); override public async Task Open() { try { - // Simple initialization Client = new BacnetClient(); + Client.OnIam += OnIamHandler; - // Parse device ID from URI (bacnet://1234) - //string deviceIdStr = TargetUri.Host; - //if (!uint.TryParse(deviceIdStr, out DeviceId)) - //return false; - - // Set timeout if specified if (TimeOutMs >= 10) + { Client.Timeout = (int)TimeOutMs; + } - // Start the client Client.Start(); - - Client.OnIam += new BacnetClient.IamHandler(handler_OnIam); - - Client.WhoIs(); - Thread.Sleep(1000); // Allow time for responses - - Console.WriteLine("Devices discovered:"); - lock (DevicesList) - { - foreach (var device in DevicesList) - { - Console.WriteLine($"Device ID: {device.device_id}, Address: {device.adr}"); - } - } - + LastActive = DateTime.Now; + await Task.Yield(); return true; } - catch + catch (Exception ex) { Client = null; return false; } } + private void OnIamHandler(BacnetClient sender, BacnetAddress adr, uint deviceId, uint maxAPDU, BacnetSegmentations segmentation, ushort vendorId) + { + // Store the device address from I-Am response + DeviceAddresses[deviceId] = adr; + } + override public bool IsConnected() { - // nothing to do, this simple http connection is stateless + // nothing to do, this simple bacnet connection is stateless return Client != null; } override public void Close() { - // nothing to do, this simple http connection is stateless + // Dispose client + if (Client != null) + { + Client.Dispose(); + Client = null; + } } - override public int UpdateItemValue(AidIfxItemStatus item) + override public async Task UpdateItemValueAsync(AidIfxItemStatus item) { - try + // access + if (item?.FormData?.Href?.HasContent() != true + || item.FormData.Bacv_useService?.HasContent() != true + || !IsConnected()) + return 0; + int res = 0; + + // GET? + if (item.FormData.Bacv_useService.Trim().ToLower() == "readproperty") { - // Example logic to update item value - if (item != null && !string.IsNullOrEmpty(item.Location)) + try { - // Simulate updating the value - item.Value = "UpdatedValue"; - return 0; // Return success code + // Extract device ID + uint deviceId = uint.Parse(TargetUri.Host); + + // Find device address + BacnetAddress deviceAddress; + if (!DeviceAddresses.ContainsKey(deviceId)) + { + // Perform WhoIs request + Client.WhoIs((int)deviceId, (int)deviceId); + await Task.Delay(1000); // Wait for response + } + + if (!DeviceAddresses.TryGetValue(deviceId, out deviceAddress)) + { + throw new Exception($"Device {deviceId} not found."); + } + + + // get object type,instance, and property + var href = item.FormData.Href.TrimStart('/'); + string[] mainParts = href.Split('/'); + string[] objectParts = mainParts[0].Split(','); + + // Create objectId + var objectType = (BacnetObjectTypes)int.Parse(objectParts[0]); + uint instance = uint.Parse(objectParts[1]); + BacnetObjectId objectId = new BacnetObjectId(objectType, instance); + + // Get property from href + var propertyId = (BacnetPropertyIds)int.Parse(mainParts[1]); + + // Read Property + IList values; + bool result = Client.ReadPropertyRequest( + deviceAddress, + objectId, + propertyId, + out values + ); + + if (result) + { + var value = values[0].Value.ToString(); + item.Value = value; + res = 1; + NotifyOutputItems(item, value); + } } - else + catch (Exception ex) { - return -1; // Return error code for invalid item + // set breakpoint in order to get failed connections! } } - catch (Exception ex) - { - // Log the exception if necessary - Console.WriteLine($"Error updating item value: {ex.Message}"); - return -2; // Return error code for exception - } - } - public class BacNode - { - public BacnetAddress adr; - public uint device_id; + return res; + } - public BacNode(BacnetAddress adr, uint device_id) - { - this.adr = adr; - this.device_id = device_id; - } - public BacnetAddress getAdd(uint device_id) - { - if (this.device_id == device_id) - return adr; - else - return null; - } - } } } \ No newline at end of file diff --git a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs index d88ce3df..a1c4e086 100644 --- a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs @@ -607,7 +607,7 @@ public class CD_AssetInterfacesDescription public List InterfaceOPCUA = new List(); [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface", Card = AasxPredefinedCardinality.ZeroToMany, - SupplSemId = "http://www.w3.org/2022/bacnet")] + SupplSemId = "http://www.w3.org/2022/bacnet")] public List InterfaceBACNET = new List(); // auto-generated informations From 65465eb6087ad6d3e4e221f8107be9b2a8a5d23c Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Tue, 22 Jul 2025 01:03:29 +0200 Subject: [PATCH 09/15] BACnet Read Write without Flipping --- .../AidBacnetConnection.cs | 182 ++++++++++-------- .../AssetInterfaceAnyUiControl.cs | 49 +++-- .../Resources/logo-bacnet.png | Bin 5712 -> 7417 bytes 3 files changed, 131 insertions(+), 100 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs index 4e844955..3edf555d 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -1,26 +1,8 @@ -using AasxIntegrationBase; -using AasxIntegrationBase.AdminShellEvents; -using AasxPluginAssetInterfaceDescription; -using AasxPredefinedConcepts; -using AasxPredefinedConcepts.AssetInterfacesDescription; -using AdminShellNS; -using AdminShellNS.DiaryData; -using Extensions; -using FluentModbus; +using AdminShellNS; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Globalization; using System.IO.BACnet; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; using System.Threading.Tasks; -using System.Web.Services.Description; using Aas = AasCore.Aas3_0; namespace AasxPluginAssetInterfaceDescription @@ -29,6 +11,8 @@ public class AidBacnetConnection : AidBaseConnection { public BacnetClient Client; private Dictionary DeviceAddresses = new Dictionary(); + public BacnetAddress deviceAddress; + override public async Task Open() { try @@ -37,17 +21,28 @@ override public async Task Open() Client.OnIam += OnIamHandler; if (TimeOutMs >= 10) - { + { Client.Timeout = (int)TimeOutMs; - } + } Client.Start(); - LastActive = DateTime.Now; - + + // Extract device ID from the URI + uint deviceId = uint.Parse(TargetUri.Host); + if (!DeviceAddresses.ContainsKey(deviceId)) + { + Client.WhoIs((int)deviceId, (int)deviceId); + await Task.Delay(1000); + } + if (!DeviceAddresses.TryGetValue(deviceId, out deviceAddress)) + { + return false; + } + await Task.Yield(); return true; } - catch (Exception ex) + catch (Exception) { Client = null; return false; @@ -62,7 +57,6 @@ private void OnIamHandler(BacnetClient sender, BacnetAddress adr, uint deviceId, override public bool IsConnected() { - // nothing to do, this simple bacnet connection is stateless return Client != null; } @@ -76,77 +70,101 @@ override public void Close() } } - override public async Task UpdateItemValueAsync(AidIfxItemStatus item) + override public int UpdateItemValue(AidIfxItemStatus item) { - // access - if (item?.FormData?.Href?.HasContent() != true - || item.FormData.Bacv_useService?.HasContent() != true - || !IsConnected()) - return 0; int res = 0; - - // GET? - if (item.FormData.Bacv_useService.Trim().ToLower() == "readproperty") + if (item?.FormData?.Href?.HasContent() != true || + item.FormData.Bacv_useService?.HasContent() != true || + !IsConnected() || + Client == null) + { + return res; + } + try { - try - { - // Extract device ID - uint deviceId = uint.Parse(TargetUri.Host); - // Find device address - BacnetAddress deviceAddress; - if (!DeviceAddresses.ContainsKey(deviceId)) - { - // Perform WhoIs request - Client.WhoIs((int)deviceId, (int)deviceId); - await Task.Delay(1000); // Wait for response - } + var href = item.FormData.Href.TrimStart('/'); + string[] mainParts = href.Split('/'); + string[] objectParts = mainParts[0].Split(','); + + var objectType = (BacnetObjectTypes)int.Parse(objectParts[0]); + uint instance = uint.Parse(objectParts[1]); + BacnetObjectId objectId = new BacnetObjectId(objectType, instance); - if (!DeviceAddresses.TryGetValue(deviceId, out deviceAddress)) + var propertyId = (BacnetPropertyIds)int.Parse(mainParts[1]); + + // READ operation + if (item.FormData.Bacv_useService.Trim().ToLower() == "readproperty") + { + try { - throw new Exception($"Device {deviceId} not found."); + IList values_r1 = new List(); + bool result_r1 = Client.ReadPropertyRequest(deviceAddress, objectId, propertyId, out values_r1); + if (result_r1 && values_r1.Count > 0 && values_r1[0].Value != null) + { + item.Value = values_r1[0].Value.ToString(); + NotifyOutputItems(item, item.Value); + res = 1; + } } - - - // get object type,instance, and property - var href = item.FormData.Href.TrimStart('/'); - string[] mainParts = href.Split('/'); - string[] objectParts = mainParts[0].Split(','); - - // Create objectId - var objectType = (BacnetObjectTypes)int.Parse(objectParts[0]); - uint instance = uint.Parse(objectParts[1]); - BacnetObjectId objectId = new BacnetObjectId(objectType, instance); - - // Get property from href - var propertyId = (BacnetPropertyIds)int.Parse(mainParts[1]); - - // Read Property - IList values; - bool result = Client.ReadPropertyRequest( - deviceAddress, - objectId, - propertyId, - out values - ); - - if (result) + catch (Exception) { - var value = values[0].Value.ToString(); - item.Value = value; - res = 1; - NotifyOutputItems(item, value); + return res; } } - catch (Exception ex) + + // WRITE operation + else if (item.FormData.Bacv_useService.Trim().ToLower() == "writeproperty") { - // set breakpoint in order to get failed connections! + try + { + if (item.MapOutputItems != null) + foreach (var moi in item.MapOutputItems) + { + // valid? + if (moi?.MapRelation?.Second == null) + continue; + + // For literal payloads + else if (moi.MapRelation.SecondHint is Aas.Property prop) + { + if (item.Value == "" || prop.Value == item.Value) + { + IList values_r2 = new List(); + bool result_r2 = Client.ReadPropertyRequest(deviceAddress, objectId, propertyId, out values_r2); + if (result_r2 && values_r2.Count > 0 && values_r2[0].Value != null && prop.Value == item.Value) + { + item.Value = values_r2[0].Value.ToString(); + NotifyOutputItems(item, item.Value); + res = 1; + } + } + else + { + float staticValue = float.Parse(prop.Value); + BacnetValue[] values_w = new BacnetValue[] { new BacnetValue(staticValue) }; + bool result_w = Client.WritePropertyRequest(deviceAddress, objectId, propertyId, values_w); + if (result_w) + { + item.Value = values_w[0].Value?.ToString(); + NotifyOutputItems(item, item.Value); + res = 1; + } + } + } + } + } + catch (Exception) + { + return res; + } } } - + catch (Exception) + { + return res; + } return res; } - - } } \ No newline at end of file diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index ac3de740..8655a23b 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -7,24 +7,25 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt). This source code may use other Open Source software components (see LICENSE.txt). */ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; using AasxIntegrationBase; -using Aas = AasCore.Aas3_0; -using AdminShellNS; -using Extensions; -using AnyUi; -using Newtonsoft.Json; +using AasxIntegrationBaseGdi; using AasxPredefinedConcepts; using AasxPredefinedConcepts.AssetInterfacesDescription; -using System.Windows.Shapes; -using AasxIntegrationBaseGdi; +using AdminShellNS; +using AnyUi; +using Extensions; using FluentModbus; -using System.Net; +using Newtonsoft.Json; using Org.BouncyCastle.Asn1.X509; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Windows.Shapes; using Workstation.ServiceModel.Ua; +using Aas = AasCore.Aas3_0; namespace AasxPluginAssetInterfaceDescription { @@ -109,6 +110,10 @@ public void Start( _dictTechnologyToBitmap = new Dictionary(); if (OperatingSystem.IsWindowsVersionAtLeast(7)) { + _dictTechnologyToBitmap.Add(AidInterfaceTechnology.BACNET, + AnyUiGdiHelper.CreateAnyUiBitmapFromResource( + "AasxPluginAssetInterfaceDesc.Resources.logo-bacnet.png", + assembly: Assembly.GetExecutingAssembly())); _dictTechnologyToBitmap.Add(AidInterfaceTechnology.HTTP, AnyUiGdiHelper.CreateAnyUiBitmapFromResource( "AasxPluginAssetInterfaceDesc.Resources.logo-http.png", @@ -125,10 +130,6 @@ public void Start( AnyUiGdiHelper.CreateAnyUiBitmapFromResource( "AasxPluginAssetInterfaceDesc.Resources.logo-opc-ua.png", assembly: Assembly.GetExecutingAssembly())); - _dictTechnologyToBitmap.Add(AidInterfaceTechnology.BACNET, - AnyUiGdiHelper.CreateAnyUiBitmapFromResource( - "AasxPluginAssetInterfaceDesc.Resources.logo-bacnet.png", - assembly: Assembly.GetExecutingAssembly())); } // fill given panel @@ -323,6 +324,13 @@ protected void RenderPanelInner( return new AnyUiLambdaActionNone(); } + // Check if at least one technology is selected + if (!_allInterfaceStatus.UseTech.Any(tech => tech)) + { + _log.Info(StoredPrint.Color.Blue, "Please select at least one technology."); + return new AnyUiLambdaActionNone(); + } + // build up data structures _allInterfaceStatus.PrepareAidInformation( _allInterfaceStatus.SmAidDescription, @@ -464,11 +472,16 @@ protected void RenderTripleRowData( if (interfaces == null) return; + // Filter interfaces based on selected technologies + var filteredInterfaces = interfaces.Where(ifx => + _allInterfaceStatus.UseTech[(int)ifx.Technology]).ToList(); + // ok var grid = view.Add(uitk.AddSmallGrid(rows: interfaces.Count, cols: 5, colWidths: new[] { "40:", "1*", "1*", "1*", "180:" })); int rowIndex = 0; - foreach (var ifx in interfaces) + + foreach (var ifx in filteredInterfaces) { // // heading @@ -660,4 +673,4 @@ private void DispatcherTimer_Tick(object sender, EventArgs e) #endregion } -} +} \ No newline at end of file diff --git a/src/AasxPluginAssetInterfaceDesc/Resources/logo-bacnet.png b/src/AasxPluginAssetInterfaceDesc/Resources/logo-bacnet.png index 7a59b61c1afc5e7eb6a13b6b6c7905545c899bb9..cefb4d60bf0df060271d956a6dbee3c4b0be2a33 100644 GIT binary patch literal 7417 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 Cp*%VO literal 5712 zcmV-W7O&}vP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!2kdb!2!6DYwZ9473E1pK~#8N?VWjC z6i3>}e^1YFh<7|!jfMa!Du+QqW{|9jOzt&mHrcFZ6P0-7z*|HFJkWR{#w4zYCQ*{S z>zinjH=k@AOtMBm#1rubG{!65SLBeHuJ@0b!I`3a?m@EQ>!&|Bzv}6(si(WT>Zz)y zfkql>q>%;K|PY79h-z(j$rRURr_e`U$InHFDiC*hIa zNc&(2^esLGpacN`0~i5jc>(}1ro(}s3NSx@GJt)SiIxap@Pf!%fJgvH2_i2LlmHZl zzvN}ql@Le?61)JxyLK~uCh{giGa}(}ub!$M4=mlq+AJ-sX&IlnnLnv3`1Ot+&}r&F z-vb1}Vk3b7K_EtL2?*!E=N7`$599OWA7f5VHS{eu0Z{V0)PYEezK_OYbLn1ue69>5 zw~pj@F(Ck85J=A_xCJ7mS8(*umMPmyFmU!@KG{iH0O-z(9R2`a@kqetyL`mAYAQbbrOEE`Xiey!(n5-(*%~qA_ zv)?{P)m4m%jPYNu3)kAP6Te;qRGr^`Ka1C8Api_F5Q#{dtub*8LL< z>(KrNvGkCBhp7G4H`%ELSLzb+qN&FcmTAo2pxj8BmzRMi?uYeIRs zmg0Pdd879Mn9C>0BI?gWU!BX#wS=ppFgI(ZM~nf`c>-2___s@&}ORwtR9l0jL8n8Yu^m!;4nvKtz&;tvpn)_t&N4 z5-Upzt1d2n;}V~4#-9Of;zbFR0IWTmegH6x7ww`rZySaOd0}8D!V^zK0x02kal0{; zL6LDy#$i|&gQ;F+sF7mPsh(Nse%?J~(0@x40;K!6f8KUxTI&)1+Uav!_dH|BL z-{)mBbO7$~qPrkG6wwSXE&e+M?=>LfeQmh?0b+na3}6h5k>P=OZyy1G(LD!+LLdPE z5kVk8Np$ZpL;wS7j_=am$a>3G%c2&)UIPYon)LqHM1tV~Y&Aw&mF3YL(6vcU3q{mx7 zh6~tpXHczIRFKJwH*82P(IBngsw#NJTC)4#o?W}kTH>$ zQFXX*+vVkNJk9H7JOW?=FWLtg>SPb%abo}g1nTMCGjemQajUHRdhBP1%(w!xcK0bl zRBe4{tm)6|4TbtKh7p6;cGY$O?fEowngmCw|Mb?XwY&}(Mm8C-N&s@rvT}gro6drR zJ@R_QwlT_zZXr!wgtVAKq^kEKMZFVq)Z3A)+ASul4vQa7I!1b?GYp>F#<$0S*ftYZ zS?%-@QB#njE3X4U<)NJyE-ah)J+GUnI#f`8dC6GW<16|^5AvC@+JPRq30K{~wy|vt zpb$V5fM@{y0H}SVQ7bz(GCBu3M3GY5E8W+x1_YMOw6!5A^(|9BYy ze0xTBYd3NAa9*!Pqqzf7Yz4jkE9mt{px5t*UVj35{T}G`UqciVpxr84YK#a>A)9Oi zE5FGN*kcSw(bb&T_2uog39{{RAFp;o+m-{oc^v?VSPz9l9Lwt_{LbAD4E*TetcO1? z`HTOCL&5$V4u$(~I282I#={ZaHXeSi>&8Qw?Y8}FqX~;J0Li>)>`zITxT*i;K|Em$ z0}L0SXR_6a@#lH&{8pe4RR?9_nt{9ypbWrzUUUoLY9+QD8-mTH44Y5fL~8bG06yls zc&spLN_UmqTA#3l*UhLi%cDupsJz-2nP|%ouj?Ab@8r6ry${2o4QAISYlTq7<{%zRSrB zfPBv`RNWM_v%B!JeVCj>bs&VR`(bWY`u#M<835myKKS;CR(2TwK~u|JJO=~7q#nqO zF^EjMuR;I>q3%Uwi!k*d%vmpg#~mmDQpb9(n)2!%^;q-Q-?f;E!f_M8G1CVkHi(p> zbcPuNV90N{C#7(*Ep740|X-E86}X<3CJ>!hnVT3B5+HsW99@ex!$<`FtM z`Vpe^SCy}xK0ahr`BB8?Zql9}6;7u|h0`yuh11#B!sz^KVf5>(VRU`zc-dARLR5iB zTr1U+uK~ktUaTc7MuU{~w!I1AG0lNGZ#oiYo$&MG1nwvj4{33?LmNHB*3zneK7w=q{qKtq49*p zXz=-QJ(9C)t6B@f)sG`({bexvfKRgEKtU1GUY`QHT2rJO463XGc~wF{6>zHx)z!Da zl-C3=#Z9HRSmEtkQQbsYrt`hqJQqX{@Q08<3&8A(rPcxj$%17JkeYo&z+Gav$?H5d zhnj7_z0BXjkQRih2V>USx*gDi`Dc(GFI~_khy?VFeFmFPFl;`~@WlycXu6g%ql}*6+6A$tRyPX})*;;@aQn_;1TSE%+Wl z?fxbhpl5`8Mzu~2W9&CxG^4NN)SHt0AFyc55HKd++;x@%JG7WP#^QNaZ^j@dpyud4 z?aejvZW1127v0bbm4cCU6O=<&Qt)HNl5z6>u26M5B&{95%K(6b`MG#8H~-yRW78&pfj%wHD%~jHc0N~D%!fR(&PP81&Gr_<_ zyXN+~h0`d`ABXv)HDEF$iMC)2*uSF+!-77=ywPDRbIkbg-3g-M^3EKK^8je2>O^~`&a zK>yHY>9al?d%yEx9OP&kjg$k)G74# z_A`DLIBX1nbRfZ#y;A#Nq!XC({^!GuP@sQE+^a9o{7wPE3BxqvE=o_8XaxWyfEo?a z0HDy&P}QVK^FBF!y5&QBVk4eK5Ub&(y9yAY^i+wqvA=L13G@%?GwS8U6$Bb=s-_yb ze<%T}T`5H{xHvTT*`@G@r}55cU?>2fP3sN;eM1Jm!oZ+ojETmjnT=chZC9^fJP8=9 zsRrQv{KL~&O*uhOWZQ`N75zQ{5X`5!LZdMpAfR7s#k}p0|7C1u)E9|azF>DYR*&%jO{_xAMuT*QfrAyN52iy005JR)x z#;~mMP*3iU_Pu%`X7artksW$>M$BYSkAeU95fs_+(fG{REeb(0yq+LAta|Cl8RTak z!=AMbpl{S;n7{HH5Skb!iVf@5S|m+39HU>qR>G1culnk$GrdWK z5+Zzwaeu|zw|DQ$IX-hy-S`uB(6xV$u0t|jpCLg11`&e*5duVz=`R`oGCe!FtNgpq zrf#wp&H_P^oiw9YKE4$YK}-aZ9t1rMae9n512cU#Y^Jp1MOU<0lMdQ0KN+08Gi*P+ zA|m31^o3WwbTyfL5=Zma{lh2pS(5DkFw7547=50VnwanUYW)!qZ&T}e!nZE;lT6}VFA%gY7`eMun62nxUK zF4bj3&)V{@2_IS<`{&)KayL!+EK;u%51Q*{r~uS1gefb!pq;JSNJ*+sXx*eABSgk_ zTkdY)MA234G<7abQ*Wbb>hEcq`bWx|Tq66s?7>N65Cf6CG4|~(LsDWk0LisRLx*rH~5fRAL zHEIeGij zX`hz!x{(k7iM$9v$w)S^fYIsXKAbN$IiHQL2B?q^F3=DO>i=NJ(HYNLe?>z*;Jh&; zqo4pdSX_+-3$F6AC+XI|yCz}BVm2x79X2IzA)A)BfKAVv$7bfGvBdlomXx2&X6Mgh ze_!#EOq19!L!&OlNa_K059iBI0=FK_$Rz*;yx6cY`kWlcsV~Xl31d_MIM0i}PpmfK zneC24tYL+ust|w46FSNw_h=~IKBIXcICjClo^vq88Jy@-UEL}J&zWu z7f`x-5iL+JrunL6G*6vDX{t<0RlQ3os`qJ*YBhZ{>05~on<=WkBy!DRr*YC4HmO%* zcoO+q3J<`|uPt@b7*^|2&%l_$7bHz*02*F2r$cB5icOAfT_;GSEl4&#NgHFFU$^0; zF>K;?Ln#HXlBS9llF>lB_3z$$=<@ifylzfYU%$B6r=qXTDVTOFC4UM_&7Z{5^54ES zw_xI)S2CYj$nS4}F_ORN`~Y;~MOSde7;>5P2&anudt8xxvGF-F~6^g zvZ6BwHcb9z9 zEo$C)|)rMNn;Q*1ay(aLw?&@Et9=Mx-( z`6wYi(A4o4AX>*IPp*@N#`G|}P(%usGGq^D3y5pa4kD&)?oSl<8@%oY{Rm{r!Pcg1?-S z^|fpR)Qs|?Q<$$>mYXyA^OgW!P_2fteP**h=t`#6YikblSvqh6l1k%6J~)Is*qXN&g%NH7NFy3T*W5ls0N=?-d? zXHi~Wek~^_=W_rn0F+vvV{RK~?+mwKdK)7HyhX!nm|dj`z~X zr~Kqe?cBL@DJLhV4S;dRnC>na0{7b(CJaw}_cXwmVMM4w{4PhiubDP4iH%6(sa9u$xHEo-xP6ol zrUM29^wx|VIj!-|u#@ug!$0KYEPK|l%&d|BO8y^PL>-k`u;LZ~0000 Date: Tue, 22 Jul 2025 15:10:25 +0200 Subject: [PATCH 10/15] Fix: Use dot '.' as decimal separator for float values --- .../AidBacnetConnection.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs index 3edf555d..f37c0a77 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -1,6 +1,7 @@ using AdminShellNS; using System; using System.Collections.Generic; +using System.Globalization; using System.IO.BACnet; using System.Threading.Tasks; using Aas = AasCore.Aas3_0; @@ -102,7 +103,8 @@ override public int UpdateItemValue(AidIfxItemStatus item) bool result_r1 = Client.ReadPropertyRequest(deviceAddress, objectId, propertyId, out values_r1); if (result_r1 && values_r1.Count > 0 && values_r1[0].Value != null) { - item.Value = values_r1[0].Value.ToString(); + float val_r1 = (float)values_r1[0].Value; + item.Value = val_r1.ToString("R", CultureInfo.InvariantCulture); NotifyOutputItems(item, item.Value); res = 1; } @@ -134,19 +136,21 @@ override public int UpdateItemValue(AidIfxItemStatus item) bool result_r2 = Client.ReadPropertyRequest(deviceAddress, objectId, propertyId, out values_r2); if (result_r2 && values_r2.Count > 0 && values_r2[0].Value != null && prop.Value == item.Value) { - item.Value = values_r2[0].Value.ToString(); + float val_r2 = (float)values_r2[0].Value; + item.Value = val_r2.ToString("R", CultureInfo.InvariantCulture); NotifyOutputItems(item, item.Value); res = 1; } } else { - float staticValue = float.Parse(prop.Value); + float staticValue = float.Parse(prop.Value, CultureInfo.InvariantCulture); BacnetValue[] values_w = new BacnetValue[] { new BacnetValue(staticValue) }; bool result_w = Client.WritePropertyRequest(deviceAddress, objectId, propertyId, values_w); if (result_w) { - item.Value = values_w[0].Value?.ToString(); + float val_r3 = (float)values_w[0].Value; + item.Value = val_r3.ToString("R", CultureInfo.InvariantCulture); NotifyOutputItems(item, item.Value); res = 1; } From a63639aa152a0e2d6da03c9401959d3d706b6665 Mon Sep 17 00:00:00 2001 From: Kazeem Oladipupo <67549739+Kaz040@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:56:00 +0100 Subject: [PATCH 11/15] Upgrade to AID version 1.1 --- src/AasxPackageExplorer.sln | 3 -- .../AasxPluginAssetInterfaceDesc.options.json | 28 ++++++++-- .../AidBacnetConnection.cs | 49 ----------------- .../AidInterfaceStatus.cs | 54 +++++++++++-------- .../AssetInterfaceAnyUiControl.cs | 2 +- .../AssetInterfaceOptions.cs | 13 +++-- src/AasxPluginAssetInterfaceDesc/Plugin.cs | 17 +++++- .../MappingsAssetInterfacesDescription.cs | 9 ++-- 8 files changed, 87 insertions(+), 88 deletions(-) diff --git a/src/AasxPackageExplorer.sln b/src/AasxPackageExplorer.sln index c1906122..15961177 100644 --- a/src/AasxPackageExplorer.sln +++ b/src/AasxPackageExplorer.sln @@ -264,7 +264,6 @@ Global {EBAE658A-3ECE-4C98-89BC-F79809AB4A5E}.ReleaseWithoutCEF|x86.ActiveCfg = Release|Any CPU {EBAE658A-3ECE-4C98-89BC-F79809AB4A5E}.ReleaseWithoutCEF|x86.Build.0 = Release|Any CPU {967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|Any CPU.Build.0 = Debug|Any CPU {967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|x64.ActiveCfg = Debug|Any CPU {967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|x64.Build.0 = Debug|Any CPU {967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -802,7 +801,6 @@ Global {7788AC2B-7F97-4755-B343-C4196FA90198}.ReleaseWithoutCEF|x86.ActiveCfg = Release|Any CPU {7788AC2B-7F97-4755-B343-C4196FA90198}.ReleaseWithoutCEF|x86.Build.0 = Release|Any CPU {2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|x64.ActiveCfg = Debug|Any CPU {2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|x64.Build.0 = Debug|Any CPU {2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -1552,7 +1550,6 @@ Global {4EB64F40-1A01-46BB-BEED-D1A75313C7F8}.ReleaseWithoutCEF|x86.ActiveCfg = Release|Any CPU {4EB64F40-1A01-46BB-BEED-D1A75313C7F8}.ReleaseWithoutCEF|x86.Build.0 = Release|Any CPU {BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|x64.ActiveCfg = Debug|Any CPU {BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|x64.Build.0 = Debug|Any CPU {BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|x86.ActiveCfg = Debug|Any CPU diff --git a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json index 1d653b26..6a29021c 100644 --- a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json +++ b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json @@ -1,7 +1,8 @@ { "Records": [ { - "IsDescription": true, + "IsDescription1_0": true, + "IsDescription1_1": false, "IsMapping": false, "AllowSubmodelSemanticId": [ { @@ -9,14 +10,31 @@ "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel" } ], - "UseHttp": false, + "UseHttp": true, "UseModbus": false, "UseMqtt": false, - "UseOpcUa": true, - "UseBacnet": true + "UseOpcUa": false, + "UseBacnet": false }, { - "IsDescription": false, + "IsDescription1_0": false, + "IsDescription1_1": true, + "IsMapping": false, + "AllowSubmodelSemanticId": [ + { + "type": "Submodel", + "value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/1/Submodel" + } + ], + "UseHttp": true, + "UseModbus": false, + "UseMqtt": false, + "UseOpcUa": false, + "UseBacnet": false + }, + { + "IsDescription1_0": false, + "IsDescription1_1": false, "IsMapping": true, "AllowSubmodelSemanticId": [ { diff --git a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs index f37c0a77..bf7aa5ea 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs @@ -114,55 +114,6 @@ override public int UpdateItemValue(AidIfxItemStatus item) return res; } } - - // WRITE operation - else if (item.FormData.Bacv_useService.Trim().ToLower() == "writeproperty") - { - try - { - if (item.MapOutputItems != null) - foreach (var moi in item.MapOutputItems) - { - // valid? - if (moi?.MapRelation?.Second == null) - continue; - - // For literal payloads - else if (moi.MapRelation.SecondHint is Aas.Property prop) - { - if (item.Value == "" || prop.Value == item.Value) - { - IList values_r2 = new List(); - bool result_r2 = Client.ReadPropertyRequest(deviceAddress, objectId, propertyId, out values_r2); - if (result_r2 && values_r2.Count > 0 && values_r2[0].Value != null && prop.Value == item.Value) - { - float val_r2 = (float)values_r2[0].Value; - item.Value = val_r2.ToString("R", CultureInfo.InvariantCulture); - NotifyOutputItems(item, item.Value); - res = 1; - } - } - else - { - float staticValue = float.Parse(prop.Value, CultureInfo.InvariantCulture); - BacnetValue[] values_w = new BacnetValue[] { new BacnetValue(staticValue) }; - bool result_w = Client.WritePropertyRequest(deviceAddress, objectId, propertyId, values_w); - if (result_w) - { - float val_r3 = (float)values_w[0].Value; - item.Value = val_r3.ToString("R", CultureInfo.InvariantCulture); - NotifyOutputItems(item, item.Value); - res = 1; - } - } - } - } - } - catch (Exception) - { - return res; - } - } } catch (Exception) { diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index d265e3fd..6b18cce1 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -7,28 +7,29 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt). This source code may use other Open Source software components (see LICENSE.txt). */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using AasxIntegrationBase; +using AasxIntegrationBase.AdminShellEvents; using AasxPredefinedConcepts; -using Aas = AasCore.Aas3_1; +using AasxPredefinedConcepts.AssetInterfacesDescription; using AdminShellNS; using AdminShellNS.DiaryData; +using AnyUi; using Extensions; -using AasxIntegrationBase; -using AasxPredefinedConcepts.AssetInterfacesDescription; using FluentModbus; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; using System.Net; +using System.Text; using System.Text.RegularExpressions; -using System.Globalization; -using AnyUi; +using System.Threading.Tasks; +using System.Windows.Controls; using System.Windows.Media.Animation; -using AasxIntegrationBase.AdminShellEvents; -using System.IO; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; +using Aas = AasCore.Aas3_1; namespace AasxPluginAssetInterfaceDescription { @@ -428,6 +429,13 @@ public T GetOrCreate(string target, LogInstance log = null) /// public class AidAllInterfaceStatus { + /// + /// Flag to enable or disable protocols in the UI based on AID version. + /// Version 1.0 will enable HTTP, MODBUS and MQTT. + /// version 1.1 will enable HTTP, MODBUS, MQTT, OPCUA and BACNET. + /// + public bool AidVersion1_1 = false; + /// /// Set to logger, if logging is desired. /// @@ -451,7 +459,7 @@ public class AidAllInterfaceStatus /// /// Current setting, which technologies shall be used. /// - public bool[] UseTech = { false, false, false, true, true }; + public bool[] UseTech = { true, false, false, false, false }; /// /// Will hold connections steady and continously update values, either by @@ -497,7 +505,7 @@ public void RememberAidSubmodel(Aas.ISubmodel sm, AssetInterfaceOptionsRecord op if (sm == null || optRec == null) return; - if (optRec.IsDescription) + if (optRec.IsDescription1_0 || optRec.IsDescription1_1) SmAidDescription = sm; if (adoptUseFlags) @@ -812,25 +820,27 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = InterfaceStatus.Clear(); if (smAid == null) return; - + // get data AID var dataAid = new AasxPredefinedConcepts.AssetInterfacesDescription.CD_AssetInterfacesDescription(); PredefinedConceptsClassMapper.ParseAasElemsToObject(smAid, dataAid, lambdaLookupReference); - + // get data MC var dataMc = (smMapping != null) ? - new AasxPredefinedConcepts.AssetInterfacesMappingConfiguration. - CD_AssetInterfacesMappingConfiguration() : null; + new AasxPredefinedConcepts.AssetInterfacesMappingConfiguration. + CD_AssetInterfacesMappingConfiguration() : null; PredefinedConceptsClassMapper.ParseAasElemsToObject(smMapping, dataMc, lambdaLookupReference); // prepare foreach (var tech in AdminShellUtil.GetEnumValues()) { var ifxs = dataAid?.InterfaceHTTP; + ifxs = null; + if (tech == AidInterfaceTechnology.HTTP) ifxs = dataAid?.InterfaceHTTP; if (tech == AidInterfaceTechnology.Modbus) ifxs = dataAid?.InterfaceMODBUS; if (tech == AidInterfaceTechnology.MQTT) ifxs = dataAid?.InterfaceMQTT; - if (tech == AidInterfaceTechnology.OPCUA) ifxs = dataAid?.InterfaceOPCUA; - if (tech == AidInterfaceTechnology.BACNET) ifxs = dataAid?.InterfaceBACNET; + if (tech == AidInterfaceTechnology.OPCUA && AidVersion1_1) ifxs = dataAid?.InterfaceOPCUA; + if (tech == AidInterfaceTechnology.BACNET && AidVersion1_1) ifxs = dataAid?.InterfaceBACNET; if (ifxs == null || ifxs.Count < 1) continue; foreach (var ifx in ifxs) diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index 07dbd2fd..f1fcf9dc 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -25,7 +25,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Reflection; using System.Windows.Shapes; using Workstation.ServiceModel.Ua; -using Aas = AasCore.Aas3_0; +using Aas = AasCore.Aas3_1; namespace AasxPluginAssetInterfaceDescription { diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs index a216ab59..9a6cc528 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceOptions.cs @@ -22,7 +22,8 @@ namespace AasxPluginAssetInterfaceDescription { public class AssetInterfaceOptionsRecord : AasxPluginOptionsLookupRecordBase { - public bool IsDescription = false; + public bool IsDescription1_0 = false; + public bool IsDescription1_1 = false; public bool IsMapping = false; public bool UseHttp = true; @@ -44,13 +45,19 @@ public static AssetInterfaceOptions CreateDefault() var defs = new DefinitionsMTP.ModuleTypePackage(); var rec1 = new AssetInterfaceOptionsRecord(); - rec1.IsDescription = true; + rec1.IsDescription1_0 = true; rec1.AllowSubmodelSemanticId = new[] { new Aas.Key(Aas.KeyTypes.Submodel, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel") }.ToList(); var rec2 = new AssetInterfaceOptionsRecord(); - rec2.IsMapping = true; + rec2.IsDescription1_1 = true; + rec1.AllowSubmodelSemanticId = new[] { + new Aas.Key(Aas.KeyTypes.Submodel, + "https://admin-shell.io/idta/AssetInterfacesDescription/1/1/Submodel") }.ToList(); + + var rec3 = new AssetInterfaceOptionsRecord(); + rec3.IsMapping = true; rec1.AllowSubmodelSemanticId = new[] { new Aas.Key(Aas.KeyTypes.Submodel, "https://admin-shell.io/idta/AssetInterfacesMappingConfiguration/1/0/Submodel") }.ToList(); diff --git a/src/AasxPluginAssetInterfaceDesc/Plugin.cs b/src/AasxPluginAssetInterfaceDesc/Plugin.cs index 374490ad..b7a07f08 100644 --- a/src/AasxPluginAssetInterfaceDesc/Plugin.cs +++ b/src/AasxPluginAssetInterfaceDesc/Plugin.cs @@ -118,9 +118,22 @@ public class Session : PluginSessionBase return null; // remember for later / background - if (foundOptRec.IsDescription) + if (foundOptRec.IsDescription1_0) + { + _allInterfaceStatus.RememberAidSubmodel(sm, foundOptRec, + adoptUseFlags: true); + _allInterfaceStatus.AidVersion1_1 = false; + } + + + // remember for later / background + if (foundOptRec.IsDescription1_1) + { _allInterfaceStatus.RememberAidSubmodel(sm, foundOptRec, adoptUseFlags: true); + _allInterfaceStatus.AidVersion1_1 = true; + } + if (foundOptRec.IsMapping) _allInterfaceStatus.RememberMappingSubmodel(sm); @@ -273,7 +286,7 @@ public class Session : PluginSessionBase if (foundOptRec == null) continue; - if (foundOptRec.IsDescription) + if (foundOptRec.IsDescription1_0) _allInterfaceStatus.RememberAidSubmodel(sm, foundOptRec, adoptUseFlags: true); if (foundOptRec.IsMapping) diff --git a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs index 4718a1c1..5bd2a697 100644 --- a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs @@ -12,6 +12,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using Extensions; using System; using System.Collections.Generic; +using static AasxPredefinedConcepts.ConceptModel.ConceptModelZveiTechnicalData; using Aas = AasCore.Aas3_1; // These classes were serialized by "export predefined concepts" @@ -20,7 +21,7 @@ This source code may use other Open Source software components (see LICENSE.txt) namespace AasxPredefinedConcepts.AssetInterfacesDescription { - + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface")] public class CD_GenericInterface { @@ -586,9 +587,9 @@ public class CD_ExternalDescriptor // auto-generated informations public AasClassMapperInfo __Info__ = null; } - + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel")] - public class CD_AssetInterfacesDescription + public class CD_AssetInterfacesDescription { [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface", Card = AasxPredefinedCardinality.ZeroToMany, SupplSemId = "http://www.w3.org/2011/http")] @@ -613,5 +614,7 @@ public class CD_AssetInterfacesDescription // auto-generated informations public AasClassMapperInfo __Info__ = null; } + + } From 4dad4d949621a3d03ccab6402a1fdeffccbe7099 Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Tue, 27 Jan 2026 16:01:06 +0100 Subject: [PATCH 12/15] Updated AID Mapping --- .../MappingsAssetInterfacesDescription.cs | 78 ++++++++++++++++--- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs index 5bd2a697..f153ead9 100644 --- a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs @@ -41,7 +41,7 @@ public class CD_GenericInterface public CD_EndpointMetadata EndpointMetadata = new CD_EndpointMetadata(); [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/InteractionMetadata", Card = AasxPredefinedCardinality.One)] - public CD_InterfaceMetadata InteractionMetadata = new CD_InterfaceMetadata(); + public CD_InteractionMetadata InteractionMetadata = new CD_InteractionMetadata(); [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/ExternalDescriptor", Card = AasxPredefinedCardinality.ZeroToOne)] public CD_ExternalDescriptor ExternalDescriptor = null; @@ -113,6 +113,12 @@ public class CD_SecurityDefinitions [AasConcept(Cd = "https://www.w3.org/2019/wot/security#OAuth2SecurityScheme", Card = AasxPredefinedCardinality.ZeroToOne)] public CD_Oauth2_sc Oauth2_sc = null; + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/OPCUASecurityChannelScheme", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Opcua_channel_sc Opcua_channel_sc = null; + + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/OPCUASecurityAuthenticationScheme ", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Opcua_authentication_sc Opcua_authentication_sc = null; + // auto-generated informations public AasClassMapperInfo __Info__ = null; } @@ -306,8 +312,46 @@ public class CD_Oauth2_sc public AasClassMapperInfo __Info__ = null; } + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#OAuth2SecurityScheme")] + public class CD_Opcua_channel_sc + { + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#SecurityScheme", Card = AasxPredefinedCardinality.One)] + public string Scheme; + + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/securityMode ", Card = AasxPredefinedCardinality.One)] + public string Uav_securityMode; + + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/securityPolicy", Card = AasxPredefinedCardinality.One)] + public string Uav_securityPolicy; + + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#proxy", Card = AasxPredefinedCardinality.ZeroToOne)] + public string Proxy; + + // auto-generated informations + public AasClassMapperInfo __Info__ = null; + } + + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#OAuth2SecurityScheme")] + public class CD_Opcua_authentication_sc + { + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#SecurityScheme", Card = AasxPredefinedCardinality.One)] + public string Scheme; + + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/userIdentityToken", Card = AasxPredefinedCardinality.One)] + public string Uav_userIdentityToken; + + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/issueToken", Card = AasxPredefinedCardinality.ZeroToOne)] + public AasClassMapperHintedReference Uav_issueToken = new AasClassMapperHintedReference(); + + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#proxy", Card = AasxPredefinedCardinality.ZeroToOne)] + public string Proxy; + + // auto-generated informations + public AasClassMapperInfo __Info__ = null; + } + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/InterfaceMetadata")] - public class CD_InterfaceMetadata + public class CD_InteractionMetadata { [AasConcept(Cd = "https://www.w3.org/2019/wot/td#PropertyAffordance", Card = AasxPredefinedCardinality.ZeroToOne)] public CD_PropertiesAffordance Properties = null; @@ -351,6 +395,9 @@ public class CD_PropertyName [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#const", Card = AasxPredefinedCardinality.ZeroToOne)] public int? Const; + [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#enum", Card = AasxPredefinedCardinality.ZeroToOne)] + public CD_Enum Enum = new CD_Enum(); + [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#default", Card = AasxPredefinedCardinality.ZeroToOne)] public string Default; @@ -375,6 +422,9 @@ public class CD_PropertyName [AasConcept(Cd = "https://www.w3.org/2019/wot/td#hasUriTemplateSchema", Card = AasxPredefinedCardinality.ZeroToOne)] public CD_Properties UriVariables = null; + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/valueSemantics", Card = AasxPredefinedCardinality.ZeroToOne)] + public AasClassMapperHintedReference ValueSemanticas = new AasClassMapperHintedReference(); + [AasConcept(Cd = "https://www.w3.org/2019/wot/td#hasForm", Card = AasxPredefinedCardinality.One)] public CD_Forms Forms = new CD_Forms(); @@ -382,6 +432,13 @@ public class CD_PropertyName public AasClassMapperInfo __Info__ = null; } + public class CD_Enum + { + + // auto-generated informations + public AasClassMapperInfo __Info__ = null; + } + [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#items")] public class CD_Items { @@ -409,6 +466,9 @@ public class CD_Items [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/lengthRange", Card = AasxPredefinedCardinality.ZeroToOne)] public AasClassMapperRange LengthRange = null; + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/valueSemantics", Card = AasxPredefinedCardinality.ZeroToOne)] + public AasClassMapperHintedReference ValueSemanticas = new AasClassMapperHintedReference(); + // auto-generated informations public AasClassMapperInfo __Info__ = null; } @@ -480,11 +540,8 @@ public class CD_Forms [AasConcept(Cd = "https://www.w3.org/2019/wot/mqtt#hasQoSFlag", Card = AasxPredefinedCardinality.ZeroToOne)] public string Mqv_qos; - [AasConcept(Cd = "https://www.w3.org/2019/wot/opc-ua#pollingTime", Card = AasxPredefinedCardinality.ZeroToOne)] - public string OpcUa_pollingTime; - - [AasConcept(Cd = "https://www.w3.org/2019/wot/opc-ua#timeout", Card = AasxPredefinedCardinality.ZeroToOne)] - public string OpcUa_timeout; + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding#browsePath", Card = AasxPredefinedCardinality.ZeroToOne)] + public string Uav_browsePath; [AasConcept(Cd = "http://www.w3.org/2022/bacnet#usesService", Card = AasxPredefinedCardinality.ZeroToOne)] public string Bacv_useService; @@ -604,7 +661,7 @@ public class CD_AssetInterfacesDescription public List InterfaceMQTT = new List(); [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface", Card = AasxPredefinedCardinality.ZeroToMany, - SupplSemId = "http://www.w3.org/2011/opc-ua")] + SupplSemId = "http://opcfoundation.org/UA/WoT-Binding")] public List InterfaceOPCUA = new List(); [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface", Card = AasxPredefinedCardinality.ZeroToMany, @@ -614,7 +671,4 @@ public class CD_AssetInterfacesDescription // auto-generated informations public AasClassMapperInfo __Info__ = null; } - - -} - +} \ No newline at end of file From bb8c83b5fe48d3240f5d0d7b1e26b55bc0449a8c Mon Sep 17 00:00:00 2001 From: PratikTarpara Date: Thu, 29 Jan 2026 14:36:57 +0100 Subject: [PATCH 13/15] Update the nodepath logic for OPCUA (AID plugin) --- .../AidInterfaceStatus.cs | 14 -------------- .../AidOpcUaConnection.cs | 9 +++++++-- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index 6b18cce1..7a8884fa 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -976,20 +976,6 @@ public void SetAidInformationForUpdateAndTimeout( ref ifc.TimeOutMs, 10.0, defaultTimeOutMs, SelectValuesToIntList(ifc?.Items?.Values, (it) => it.FormData?.Modv_timeout)); } - - // for OPC UA, analyze update frequency and timeout - foreach (var ifc in InterfaceStatus.Where((i) => i.Technology == AidInterfaceTechnology.OPCUA)) - { - // polltimes - SetDoubleOnDefaultOrAvgOfIntList( - ref ifc.UpdateFreqMs, 10.0, defaultUpdateFreqMs, - SelectValuesToIntList(ifc?.Items?.Values, (it) => it.FormData?.OpcUa_pollingTime)); - - // time out - SetDoubleOnDefaultOrAvgOfIntList( - ref ifc.TimeOutMs, 10.0, defaultTimeOutMs, - SelectValuesToIntList(ifc?.Items?.Values, (it) => it.FormData?.OpcUa_timeout)); - } } } diff --git a/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs index 0778dd59..6db5c175 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs @@ -138,8 +138,10 @@ override public async Task UpdateItemValueAsync(AidIfxItemStatus item) // careful try { + var nodePath = "" + item.FormData?.Href; + nodePath = nodePath.Replace("/?id =", "").Trim(); // get an node id? - var nid = Client.ParseAndCreateNodeId(item?.FormData?.Href); + var nid = Client.ParseAndCreateNodeId(nodePath); // direct read possible? var dv = await Client.ReadNodeIdAsync(nid); @@ -198,11 +200,14 @@ override public async Task PrepareContinousRunAsync(IEnumerable Date: Thu, 5 Feb 2026 10:35:57 +0100 Subject: [PATCH 14/15] Adapt AID OPC UA to version 1.1. ADD OPC UA security options to base OPC UA client used by MTP and AID --- src/AasxOpcUa2Client/AasOpcUaClient2.cs | 112 +++++++++++-- .../AidInterfaceStatus.cs | 155 +++++++++++++++++- .../AidOpcUaConnection.cs | 12 +- .../MappingsAssetInterfacesDescription.cs | 24 +-- 4 files changed, 280 insertions(+), 23 deletions(-) diff --git a/src/AasxOpcUa2Client/AasOpcUaClient2.cs b/src/AasxOpcUa2Client/AasOpcUaClient2.cs index 00d85657..49bfa821 100644 --- a/src/AasxOpcUa2Client/AasOpcUaClient2.cs +++ b/src/AasxOpcUa2Client/AasOpcUaClient2.cs @@ -19,6 +19,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Threading.Tasks; using AasxIntegrationBase; using AdminShellNS; +using AnyUi; using Workstation.ServiceModel.Ua; using Workstation.ServiceModel.Ua.Channels; @@ -58,12 +59,22 @@ public class AasOpcUaClient2 protected string _password; protected uint _timeOutMs = 2000; + //Optional parameters for secured OPC UA interface in AID + protected MessageSecurityMode _securityMode; + protected string _securityPolicy; + protected static bool _autoConnect = false; + protected ClientSessionChannel _channel = null; - public AasOpcUaClient2(string endpointURL, bool autoAccept, + public AasOpcUaClient2( + string endpointURL, + bool autoAccept, string userName, string password, uint timeOutMs = 2000, - LogInstance log = null) + LogInstance log = null, + MessageSecurityMode? securityMode = MessageSecurityMode.None, + string? securityPolicy = SecurityPolicyUris.None, + bool? autoConnect = false) { _endpointURL = endpointURL; _autoAccept = autoAccept; @@ -71,6 +82,9 @@ public AasOpcUaClient2(string endpointURL, bool autoAccept, _password = password; _timeOutMs = timeOutMs; _log = log; + _securityMode = (MessageSecurityMode)securityMode; + _securityPolicy = securityPolicy; + _autoConnect = (bool)autoConnect; } public async Task DirectConnect() @@ -98,19 +112,97 @@ public async Task StartClientAsync() ApplicationType = ApplicationType.Client }; - // create a 'ClientSessionChannel', a client-side channel that opens a 'session' with the server. - _channel = new ClientSessionChannel( - clientDescription, - null, // no x509 certificates - new AnonymousIdentity(), // no user identity - "" + _endpointURL, - SecurityPolicyUris.None); // no encryption + // Create Endpoint + var Endpoint = new EndpointDescription + { + EndpointUrl = _endpointURL, + SecurityPolicyUri = _securityPolicy, + SecurityMode = _securityMode, + }; + + /// + ///Set up auto connection by getting all endpoints from server and using one of these endpoints + ///Used by AID Submodel + /// + if (_autoConnect) + { + + var endpointRequest = new GetEndpointsRequest() + { + EndpointUrl = _endpointURL, + }; + GetEndpointsResponse endpoints = await DiscoveryService.GetEndpointsAsync(endpointRequest); + if (endpoints.Endpoints != null) + { + foreach (var endpoint in endpoints.Endpoints) + { + if (endpoint.SecurityMode.ToString().ToLower() == "none" && + endpoint.SecurityPolicyUri.Split("#").Last().ToLower() == "none") + { + _log?.Info("Using auto connect option..."); + _log?.Info($"Creating channel from one of {endpoints.Endpoints.Length} Endpoints"); + _log?.Info($"Using Endpoint : Security Mode = {endpoint.SecurityMode} and Security Policy: {endpoint.SecurityPolicyUri.Split("#").Last()}"); + Endpoint.EndpointUrl = endpoint.EndpointUrl; + Endpoint.SecurityMode = endpoint.SecurityMode; + Endpoint.SecurityPolicyUri = endpoint.SecurityPolicyUri; + _channel = new ClientSessionChannel( + clientDescription, + null, // no x509 certificates + new AnonymousIdentity(), // no user identity + Endpoint); + + } + break; + } + } + + } + + /// + ///Set up Secure Session. Used by AID Submodel + /// + + else if (_securityMode != MessageSecurityMode.None && _securityPolicy != SecurityPolicyUris.None) + { + /// + ///create directory for client certificate. + ///certificate will be created if none is existing. + ///If the server does not auto accept certificates, it is mandatory to copy this client's public key + ///in ./pki/own/certs folder and add it to the server's trusted folders + /// + _log?.Info($"....Creating Secure channel with security mode: {_securityMode} and security policy: {_securityPolicy}"); + var certificatestore = new DirectoryStore("./pki"); + // create a 'ClientSessionChannel', a client-side channel that opens a 'session' with the server. + _channel = new ClientSessionChannel( + clientDescription, + certificatestore, + new AnonymousIdentity(), // no user identity + Endpoint // endpoint built for opc ua security + ); + + } + /// + /// Set up Unsecure Session. Used by AID, MTP and UaClient plugins + /// + + else if (_securityMode == MessageSecurityMode.None) + { + // create a 'ClientSessionChannel', a client-side channel that opens a 'session' with the server. + _log?.Info($"....Creating Unsecure channel with security mode: {_securityMode} and security policy: {_securityPolicy}"); + _channel = new ClientSessionChannel( + clientDescription, + null, // no x509 certificates + new AnonymousIdentity(), // no user identity + + Endpoint); + } // try opening a session and reading a few nodes. try { await _channel.OpenAsync(); - } catch (Exception ex) + } + catch (Exception ex) { ClientStatus = AasOpcUaClientStatus.ErrorCreateSession; _log?.Error(ex, "open async"); diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index 7a8884fa..26031636 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -7,6 +7,7 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt). This source code may use other Open Source software components (see LICENSE.txt). */ +using AasCore.Aas3_1; using AasxIntegrationBase; using AasxIntegrationBase.AdminShellEvents; using AasxPredefinedConcepts; @@ -29,6 +30,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Threading.Tasks; using System.Windows.Controls; using System.Windows.Media.Animation; +using static System.Net.WebRequestMethods; using Aas = AasCore.Aas3_1; namespace AasxPluginAssetInterfaceDescription @@ -129,6 +131,27 @@ public class AidInterfaceStatus /// public string EndpointBase = ""; + /// + /// For creating OPC UA secured channel. it can be either None, Sign, SignAndEncrypt + /// + + public int SecurityMode = 1; + + /// + /// For creating OPC UA security channel. For this version, only + /// None, Basic256Sha256, Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss, + /// Outdated(not recommended policies):Basic256, Basic128Rsa15 + /// + + public string SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#None"; + + /// + /// For creating OPC UA security channel. + /// when AutoConnection is true, the client gets endpoints from server + /// and pick one from the list of endpoints to set up the session. + /// + + public bool OPCAutoConnection = false; /// /// Used by byteStream payload for decoding, presently, mainly used by Modbus but other protocols will also be using it @@ -258,6 +281,28 @@ public class AidBaseConnection /// public string Password = null; + /// + /// For creating OPC UA secured channel. it can be either None, Sign, SignAndEncrypt + /// + + public int SecurityMode = 1; + + /// + /// For creating OPC UA security channel. For this version, only + /// None, Basic256Sha256, Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss, + /// Outdated(not recommended policies):Basic256, Basic128Rsa15 + /// + + public string SecurityPolicy = null; + + /// + /// For creating OPC UA security channel. + /// when AutoConnection is true, the client gets endpoints from server + /// and pick one from the list of endpoints to set up the session. + /// + + public bool OPCAutoConnection = false; + /// /// Used by byteStream payload for decoding, presently, mainly used by Modbus but other protocols will also be using it /// @@ -550,6 +595,9 @@ protected AidBaseConnection GetOrCreate( case AidInterfaceTechnology.OPCUA: conn = OpcUaConnections.GetOrCreate(endpointBase, log); + conn.OPCAutoConnection = ifcStatus.OPCAutoConnection; + conn.SecurityMode = ifcStatus.SecurityMode; + conn.SecurityPolicy = ifcStatus.SecurityPolicy; break; case AidInterfaceTechnology.BACNET: @@ -820,11 +868,11 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = InterfaceStatus.Clear(); if (smAid == null) return; - + // get data AID var dataAid = new AasxPredefinedConcepts.AssetInterfacesDescription.CD_AssetInterfacesDescription(); PredefinedConceptsClassMapper.ParseAasElemsToObject(smAid, dataAid, lambdaLookupReference); - + // get data MC var dataMc = (smMapping != null) ? new AasxPredefinedConcepts.AssetInterfacesMappingConfiguration. @@ -849,6 +897,7 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = var dn = AdminShellUtil.TakeFirstContent(ifx.Title, ifx.__Info__?.Referable?.IdShort); var aidIfx = new AidInterfaceStatus() { + Technology = tech, DisplayName = $"{dn}", Info = $"{ifx.EndpointMetadata?.Base}", @@ -911,10 +960,112 @@ public void PrepareAidInformation(Aas.ISubmodel smAid, Aas.ISubmodel smMapping = continue; foreach (var propName in ifx.InteractionMetadata?.Properties?.Property) recurseProp("\u2302", propName); + + //Handling of opc ua security + if (aidIfx.Technology == AidInterfaceTechnology.OPCUA) + { + ExtractSecurityData(ifx, aidIfx); + + } + } } } + protected void ExtractSecurityData(CD_GenericInterface ifx, AidInterfaceStatus aidIfx) + { + if (ifx.EndpointMetadata.Security.SecurityRef.Count > 0 && + ifx.EndpointMetadata.Security.SecurityRef[0].ValueHint != null && + ifx.EndpointMetadata.Security.SecurityRef[0].ValueHint is SubmodelElementCollection securityRef) + { + + if (securityRef.SemanticId?.GetAsExactlyOneKey().Value == "https://www.w3.org/2019/wot/security#NoSecurityScheme") + { + aidIfx.SecurityMode = 1; + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#None"; + return; + } + else if (securityRef.SemanticId?.GetAsExactlyOneKey().Value == "https://www.w3.org/2019/wot/security#AutoSecurityScheme") + { + aidIfx.OPCAutoConnection = true; + return; + + } + else if(securityRef.SemanticId?.GetAsExactlyOneKey().Value == "https://www.w3.org/2019/wot/security#ComboSecurityScheme") + { + //Combo Security implementation here + // Handling allOf + foreach(var opcuaSecurityRef in ifx.EndpointMetadata.SecurityDefinitions.Combo_sc.AllOf.SecurityRef) + { + if(opcuaSecurityRef != null && opcuaSecurityRef.ValueHint is SubmodelElementCollection opcuasec) + { + if(opcuasec.SemanticId.GetAsExactlyOneKey().Value == "http://opcfoundation.org/UA/WoT-Binding/OPCUASecurityChannelScheme") + { + switch(ifx.EndpointMetadata.SecurityDefinitions.Opcua_channel_sc.Uav_securityMode.ToLower()) + { + case "none": + aidIfx.SecurityMode = 1; + break; + case "sign": + aidIfx.SecurityMode = 2; + break; + case "signandencrypt": + aidIfx.SecurityMode = 3; + break; + + } + switch (ifx.EndpointMetadata.SecurityDefinitions.Opcua_channel_sc.Uav_securityPolicy.ToLower()) + { + case "basic256sha256": + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"; + break; + case "aes128_sha256_rsaoaep": + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"; + break; + case "aes256_sha256_rsapss": + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Aes256_Sha256_RsaPss"; + break; + case "basic256": + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Basic256"; + break; + case "basic128rsa15": + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"; + break; + } + } + //else if (opcuasec.SemanticId.GetAsExactlyOneKey().Value == "http://opcfoundation.org/UA/WoT-Binding/OPCUASecurityAuthenticationScheme") + //{ + // switch(ifx.EndpointMetadata.SecurityDefinitions.Opcua_authentication_sc.Uav_userIdentityToken.ToLower()) + // { + // case "anonymous": + // aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"; + // break; + // case "username": + // aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"; + // break; + // } + + //} + } + + + } + return; + } + else + { + aidIfx.SecurityMode = 1; + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#None"; + return; + } + } + else + { + aidIfx.SecurityMode = 1; + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#None"; + return; + } + } protected List SelectValuesToIntList( IEnumerable items, Func selectStringValue) where ITEM : class diff --git a/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs index 6db5c175..f0cd3e51 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs @@ -29,6 +29,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using MQTTnet.Client; using System.Web.Services.Description; using AasxOpcUa2Client; +using System.IO; + #if OPCUA2 @@ -75,13 +77,18 @@ override public async Task Open() // make client // use the full target uri as endpoint (first) #if OPCUA2 + Client = new AasOpcUaClient2( TargetUri.ToString(), autoAccept: true, userName: this.User, password: this.Password, timeOutMs: (TimeOutMs >= 10) ? (uint)TimeOutMs : 2000, - log: Log); + log: Log, + securityMode: (MessageSecurityMode?)this.SecurityMode, + securityPolicy: this.SecurityPolicy, + autoConnect: this.OPCAutoConnection); + #else Client = new AasOpcUaClient( TargetUri.ToString(), @@ -146,6 +153,9 @@ override public async Task UpdateItemValueAsync(AidIfxItemStatus item) // direct read possible? var dv = await Client.ReadNodeIdAsync(nid); item.Value = AdminShellUtil.ToStringInvariant(dv?.Value); + + // notify + NotifyOutputItems(item, item.Value); LastActive = DateTime.Now; // success diff --git a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs index f153ead9..2c012f6e 100644 --- a/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs +++ b/src/AasxPredefinedConcepts/Mappings/MappingsAssetInterfacesDescription.cs @@ -78,7 +78,9 @@ public class CD_EndpointMetadata [AasConcept(Cd = "https://www.w3.org/2019/wot/td#hasSecurityConfiguration")] public class CD_Security { - + [AasConcept(Cd = "https://www.w3.org/2019/wot/td#definesSecurityScheme", Card = AasxPredefinedCardinality.ZeroToMany)] + public List SecurityRef = new List(); + // auto-generated informations public AasClassMapperInfo __Info__ = null; } @@ -171,10 +173,10 @@ public class CD_Combo_sc [AasConcept(Cd = "https://www.w3.org/2019/wot/security#SecurityScheme", Card = AasxPredefinedCardinality.One)] public string Scheme; - [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#oneOf", Card = AasxPredefinedCardinality.One)] + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#oneOf", Card = AasxPredefinedCardinality.One)] public CD_OneOf OneOf = new CD_OneOf(); - [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#allOf", Card = AasxPredefinedCardinality.One)] + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#allOf", Card = AasxPredefinedCardinality.One)] public CD_AllOf AllOf = new CD_AllOf(); [AasConcept(Cd = "https://www.w3.org/2019/wot/security#proxy", Card = AasxPredefinedCardinality.ZeroToOne)] @@ -184,18 +186,20 @@ public class CD_Combo_sc public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#oneOf")] + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#oneOf")] public class CD_OneOf { - + [AasConcept(Cd = "https://www.w3.org/2019/wot/td#definesSecurityScheme", Card = AasxPredefinedCardinality.ZeroToMany)] + public List SecurityRef = new List(); // auto-generated informations public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "https://www.w3.org/2019/wot/json-schema#allOf")] + [AasConcept(Cd = "https://www.w3.org/2019/wot/security#allOf")] public class CD_AllOf { - + [AasConcept(Cd = "https://www.w3.org/2019/wot/td#definesSecurityScheme", Card = AasxPredefinedCardinality.ZeroToMany)] + public List SecurityRef = new List(); // auto-generated informations public AasClassMapperInfo __Info__ = null; } @@ -312,7 +316,7 @@ public class CD_Oauth2_sc public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "https://www.w3.org/2019/wot/security#OAuth2SecurityScheme")] + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/OPCUASecurityChannelScheme")] public class CD_Opcua_channel_sc { [AasConcept(Cd = "https://www.w3.org/2019/wot/security#SecurityScheme", Card = AasxPredefinedCardinality.One)] @@ -331,7 +335,7 @@ public class CD_Opcua_channel_sc public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "https://www.w3.org/2019/wot/security#OAuth2SecurityScheme")] + [AasConcept(Cd = "http://opcfoundation.org/UA/WoT-Binding/OPCUASecurityAuthenticationScheme")] public class CD_Opcua_authentication_sc { [AasConcept(Cd = "https://www.w3.org/2019/wot/security#SecurityScheme", Card = AasxPredefinedCardinality.One)] @@ -350,7 +354,7 @@ public class CD_Opcua_authentication_sc public AasClassMapperInfo __Info__ = null; } - [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/InterfaceMetadata")] + [AasConcept(Cd = "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/InteractionMetadata")] public class CD_InteractionMetadata { [AasConcept(Cd = "https://www.w3.org/2019/wot/td#PropertyAffordance", Card = AasxPredefinedCardinality.ZeroToOne)] From 58413adffdc6074f559fdd9a687206871a7d44f6 Mon Sep 17 00:00:00 2001 From: Kazeem Oladipupo <67549739+Kaz040@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:34:38 +0100 Subject: [PATCH 15/15] BugFix: OPC UA part of AID 1.1 --- .../AidInterfaceStatus.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index 26031636..5b416085 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -1012,6 +1012,10 @@ protected void ExtractSecurityData(CD_GenericInterface ifx, AidInterfaceStatus a case "signandencrypt": aidIfx.SecurityMode = 3; break; + default: + aidIfx.SecurityMode = 1; + break; + } switch (ifx.EndpointMetadata.SecurityDefinitions.Opcua_channel_sc.Uav_securityPolicy.ToLower()) @@ -1031,23 +1035,13 @@ protected void ExtractSecurityData(CD_GenericInterface ifx, AidInterfaceStatus a case "basic128rsa15": aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15"; break; + default: + aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#None"; + break; } } - //else if (opcuasec.SemanticId.GetAsExactlyOneKey().Value == "http://opcfoundation.org/UA/WoT-Binding/OPCUASecurityAuthenticationScheme") - //{ - // switch(ifx.EndpointMetadata.SecurityDefinitions.Opcua_authentication_sc.Uav_userIdentityToken.ToLower()) - // { - // case "anonymous": - // aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"; - // break; - // case "username": - // aidIfx.SecurityPolicy = "http://opcfoundation.org/UA/SecurityPolicy#Aes128_Sha256_RsaOaep"; - // break; - // } - - //} + } - } return;