Skip to content

Commit 59e9724

Browse files
Update: Asset Interfaces Description Version 1.0 to Version 1.1 (#325)
* add bacnet project * Add Bacnet logo * Update BACnet logo * Edit MappingAssetInterfacesDescription * Changes * Update BACnet Plugin * Bacnet Implemntation Chnages * Bacnet Working Implimentation * BACnet Read Write without Flipping * Fix: Use dot '.' as decimal separator for float values * Upgrade to AID version 1.1 * Updated AID Mapping * Update the nodepath logic for OPCUA (AID plugin) * Adapt AID OPC UA to version 1.1. ADD OPC UA security options to base OPC UA client used by MTP and AID * BugFix: OPC UA part of AID 1.1 --------- Co-authored-by: PratikTarpara <pratik221297@gmail.com>
1 parent 384d826 commit 59e9724

12 files changed

Lines changed: 688 additions & 122 deletions

src/AasxOpcUa2Client/AasOpcUaClient2.cs

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This source code may use other Open Source software components (see LICENSE.txt)
1919
using System.Threading.Tasks;
2020
using AasxIntegrationBase;
2121
using AdminShellNS;
22+
using AnyUi;
2223

2324
using Workstation.ServiceModel.Ua;
2425
using Workstation.ServiceModel.Ua.Channels;
@@ -58,19 +59,32 @@ public class AasOpcUaClient2
5859
protected string _password;
5960
protected uint _timeOutMs = 2000;
6061

62+
//Optional parameters for secured OPC UA interface in AID
63+
protected MessageSecurityMode _securityMode;
64+
protected string _securityPolicy;
65+
protected static bool _autoConnect = false;
66+
6167
protected ClientSessionChannel _channel = null;
6268

63-
public AasOpcUaClient2(string endpointURL, bool autoAccept,
69+
public AasOpcUaClient2(
70+
string endpointURL,
71+
bool autoAccept,
6472
string userName, string password,
6573
uint timeOutMs = 2000,
66-
LogInstance log = null)
74+
LogInstance log = null,
75+
MessageSecurityMode? securityMode = MessageSecurityMode.None,
76+
string? securityPolicy = SecurityPolicyUris.None,
77+
bool? autoConnect = false)
6778
{
6879
_endpointURL = endpointURL;
6980
_autoAccept = autoAccept;
7081
_userName = userName;
7182
_password = password;
7283
_timeOutMs = timeOutMs;
7384
_log = log;
85+
_securityMode = (MessageSecurityMode)securityMode;
86+
_securityPolicy = securityPolicy;
87+
_autoConnect = (bool)autoConnect;
7488
}
7589

7690
public async Task DirectConnect()
@@ -98,19 +112,97 @@ public async Task StartClientAsync()
98112
ApplicationType = ApplicationType.Client
99113
};
100114

101-
// create a 'ClientSessionChannel', a client-side channel that opens a 'session' with the server.
102-
_channel = new ClientSessionChannel(
103-
clientDescription,
104-
null, // no x509 certificates
105-
new AnonymousIdentity(), // no user identity
106-
"" + _endpointURL,
107-
SecurityPolicyUris.None); // no encryption
115+
// Create Endpoint
116+
var Endpoint = new EndpointDescription
117+
{
118+
EndpointUrl = _endpointURL,
119+
SecurityPolicyUri = _securityPolicy,
120+
SecurityMode = _securityMode,
121+
};
122+
123+
///<summary>
124+
///Set up auto connection by getting all endpoints from server and using one of these endpoints
125+
///Used by AID Submodel
126+
///</summary>
127+
if (_autoConnect)
128+
{
129+
130+
var endpointRequest = new GetEndpointsRequest()
131+
{
132+
EndpointUrl = _endpointURL,
133+
};
134+
GetEndpointsResponse endpoints = await DiscoveryService.GetEndpointsAsync(endpointRequest);
135+
if (endpoints.Endpoints != null)
136+
{
137+
foreach (var endpoint in endpoints.Endpoints)
138+
{
139+
if (endpoint.SecurityMode.ToString().ToLower() == "none" &&
140+
endpoint.SecurityPolicyUri.Split("#").Last().ToLower() == "none")
141+
{
142+
_log?.Info("Using auto connect option...");
143+
_log?.Info($"Creating channel from one of {endpoints.Endpoints.Length} Endpoints");
144+
_log?.Info($"Using Endpoint : Security Mode = {endpoint.SecurityMode} and Security Policy: {endpoint.SecurityPolicyUri.Split("#").Last()}");
145+
Endpoint.EndpointUrl = endpoint.EndpointUrl;
146+
Endpoint.SecurityMode = endpoint.SecurityMode;
147+
Endpoint.SecurityPolicyUri = endpoint.SecurityPolicyUri;
148+
_channel = new ClientSessionChannel(
149+
clientDescription,
150+
null, // no x509 certificates
151+
new AnonymousIdentity(), // no user identity
152+
Endpoint);
153+
154+
}
155+
break;
156+
}
157+
}
158+
159+
}
160+
161+
///<summary>
162+
///Set up Secure Session. Used by AID Submodel
163+
///</summary>
164+
165+
else if (_securityMode != MessageSecurityMode.None && _securityPolicy != SecurityPolicyUris.None)
166+
{
167+
///<summary>
168+
///create directory for client certificate.
169+
///certificate will be created if none is existing.
170+
///If the server does not auto accept certificates, it is mandatory to copy this client's public key
171+
///in ./pki/own/certs folder and add it to the server's trusted folders
172+
///</summary>
173+
_log?.Info($"....Creating Secure channel with security mode: {_securityMode} and security policy: {_securityPolicy}");
174+
var certificatestore = new DirectoryStore("./pki");
175+
// create a 'ClientSessionChannel', a client-side channel that opens a 'session' with the server.
176+
_channel = new ClientSessionChannel(
177+
clientDescription,
178+
certificatestore,
179+
new AnonymousIdentity(), // no user identity
180+
Endpoint // endpoint built for opc ua security
181+
);
182+
183+
}
184+
///<summary>
185+
/// Set up Unsecure Session. Used by AID, MTP and UaClient plugins
186+
///</summary>
187+
188+
else if (_securityMode == MessageSecurityMode.None)
189+
{
190+
// create a 'ClientSessionChannel', a client-side channel that opens a 'session' with the server.
191+
_log?.Info($"....Creating Unsecure channel with security mode: {_securityMode} and security policy: {_securityPolicy}");
192+
_channel = new ClientSessionChannel(
193+
clientDescription,
194+
null, // no x509 certificates
195+
new AnonymousIdentity(), // no user identity
196+
197+
Endpoint);
198+
}
108199

109200
// try opening a session and reading a few nodes.
110201
try
111202
{
112203
await _channel.OpenAsync();
113-
} catch (Exception ex)
204+
}
205+
catch (Exception ex)
114206
{
115207
ClientStatus = AasOpcUaClientStatus.ErrorCreateSession;
116208
_log?.Error(ex, "open async");

src/AasxPackageExplorer.sln

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ Global
264264
{EBAE658A-3ECE-4C98-89BC-F79809AB4A5E}.ReleaseWithoutCEF|x86.ActiveCfg = Release|Any CPU
265265
{EBAE658A-3ECE-4C98-89BC-F79809AB4A5E}.ReleaseWithoutCEF|x86.Build.0 = Release|Any CPU
266266
{967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
267-
{967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|Any CPU.Build.0 = Debug|Any CPU
268267
{967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|x64.ActiveCfg = Debug|Any CPU
269268
{967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|x64.Build.0 = Debug|Any CPU
270269
{967E60E3-D668-42A3-AA0B-1A031C20D871}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -802,7 +801,6 @@ Global
802801
{7788AC2B-7F97-4755-B343-C4196FA90198}.ReleaseWithoutCEF|x86.ActiveCfg = Release|Any CPU
803802
{7788AC2B-7F97-4755-B343-C4196FA90198}.ReleaseWithoutCEF|x86.Build.0 = Release|Any CPU
804803
{2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
805-
{2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
806804
{2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|x64.ActiveCfg = Debug|Any CPU
807805
{2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|x64.Build.0 = Debug|Any CPU
808806
{2F21FEFF-F0EF-40B5-BA05-09FC9F499AE9}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -1552,7 +1550,6 @@ Global
15521550
{4EB64F40-1A01-46BB-BEED-D1A75313C7F8}.ReleaseWithoutCEF|x86.ActiveCfg = Release|Any CPU
15531551
{4EB64F40-1A01-46BB-BEED-D1A75313C7F8}.ReleaseWithoutCEF|x86.Build.0 = Release|Any CPU
15541552
{BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1555-
{BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
15561553
{BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|x64.ActiveCfg = Debug|Any CPU
15571554
{BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|x64.Build.0 = Debug|Any CPU
15581555
{BE68E42C-28CB-4298-9F34-A18AF92FC4DE}.Debug|x86.ActiveCfg = Debug|Any CPU

src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<None Remove="Resources\logo-modbus.png" />
1616
<None Remove="Resources\logo-mqtt.png" />
1717
<None Remove="Resources\logo-opc-ua.png" />
18+
<None Remove="Resources\logo-bacnet.png" />
1819
</ItemGroup>
1920
<ItemGroup>
2021
<None Update="AasxPluginAssetInterfaceDesc.options.json">
@@ -33,6 +34,7 @@
3334
<EmbeddedResource Include="Resources\logo-modbus.png" />
3435
<EmbeddedResource Include="Resources\logo-mqtt.png" />
3536
<EmbeddedResource Include="Resources\logo-opc-ua.png" />
37+
<EmbeddedResource Include="Resources\logo-bacnet.png" />
3638
</ItemGroup>
3739
<ItemGroup>
3840
<ProjectReference Include="..\AasxIntegrationBaseGdi\AasxIntegrationBaseGdi.csproj" />
@@ -42,6 +44,7 @@
4244
<ProjectReference Include="..\AasxPredefinedConcepts\AasxPredefinedConcepts.csproj" />
4345
</ItemGroup>
4446
<ItemGroup>
47+
<PackageReference Include="BACnet" Version="3.0.1-beta6" />
4548
<PackageReference Include="FluentModbus" Version="5.0.3" />
4649
<PackageReference Include="MQTTnet" Version="4.1.1.318" />
4750
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.options.json

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
11
{
22
"Records": [
33
{
4-
"IsDescription": true,
4+
"IsDescription1_0": true,
5+
"IsDescription1_1": false,
56
"IsMapping": false,
67
"AllowSubmodelSemanticId": [
78
{
89
"type": "Submodel",
910
"value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel"
1011
}
1112
],
12-
"UseHttp": false,
13+
"UseHttp": true,
1314
"UseModbus": false,
1415
"UseMqtt": false,
15-
"UseOpcUa": true
16+
"UseOpcUa": false,
17+
"UseBacnet": false
1618
},
1719
{
18-
"IsDescription": false,
20+
"IsDescription1_0": false,
21+
"IsDescription1_1": true,
22+
"IsMapping": false,
23+
"AllowSubmodelSemanticId": [
24+
{
25+
"type": "Submodel",
26+
"value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/1/Submodel"
27+
}
28+
],
29+
"UseHttp": true,
30+
"UseModbus": false,
31+
"UseMqtt": false,
32+
"UseOpcUa": false,
33+
"UseBacnet": false
34+
},
35+
{
36+
"IsDescription1_0": false,
37+
"IsDescription1_1": false,
1938
"IsMapping": true,
2039
"AllowSubmodelSemanticId": [
2140
{
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using AdminShellNS;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Globalization;
5+
using System.IO.BACnet;
6+
using System.Threading.Tasks;
7+
using Aas = AasCore.Aas3_0;
8+
9+
namespace AasxPluginAssetInterfaceDescription
10+
{
11+
public class AidBacnetConnection : AidBaseConnection
12+
{
13+
public BacnetClient Client;
14+
private Dictionary<uint, BacnetAddress> DeviceAddresses = new Dictionary<uint, BacnetAddress>();
15+
public BacnetAddress deviceAddress;
16+
17+
override public async Task<bool> Open()
18+
{
19+
try
20+
{
21+
Client = new BacnetClient();
22+
Client.OnIam += OnIamHandler;
23+
24+
if (TimeOutMs >= 10)
25+
{
26+
Client.Timeout = (int)TimeOutMs;
27+
}
28+
29+
Client.Start();
30+
31+
// Extract device ID from the URI
32+
uint deviceId = uint.Parse(TargetUri.Host);
33+
if (!DeviceAddresses.ContainsKey(deviceId))
34+
{
35+
Client.WhoIs((int)deviceId, (int)deviceId);
36+
await Task.Delay(1000);
37+
}
38+
if (!DeviceAddresses.TryGetValue(deviceId, out deviceAddress))
39+
{
40+
return false;
41+
}
42+
43+
await Task.Yield();
44+
return true;
45+
}
46+
catch (Exception)
47+
{
48+
Client = null;
49+
return false;
50+
}
51+
}
52+
53+
private void OnIamHandler(BacnetClient sender, BacnetAddress adr, uint deviceId, uint maxAPDU, BacnetSegmentations segmentation, ushort vendorId)
54+
{
55+
// Store the device address from I-Am response
56+
DeviceAddresses[deviceId] = adr;
57+
}
58+
59+
override public bool IsConnected()
60+
{
61+
return Client != null;
62+
}
63+
64+
override public void Close()
65+
{
66+
// Dispose client
67+
if (Client != null)
68+
{
69+
Client.Dispose();
70+
Client = null;
71+
}
72+
}
73+
74+
override public int UpdateItemValue(AidIfxItemStatus item)
75+
{
76+
int res = 0;
77+
if (item?.FormData?.Href?.HasContent() != true ||
78+
item.FormData.Bacv_useService?.HasContent() != true ||
79+
!IsConnected() ||
80+
Client == null)
81+
{
82+
return res;
83+
}
84+
try
85+
{
86+
87+
var href = item.FormData.Href.TrimStart('/');
88+
string[] mainParts = href.Split('/');
89+
string[] objectParts = mainParts[0].Split(',');
90+
91+
var objectType = (BacnetObjectTypes)int.Parse(objectParts[0]);
92+
uint instance = uint.Parse(objectParts[1]);
93+
BacnetObjectId objectId = new BacnetObjectId(objectType, instance);
94+
95+
var propertyId = (BacnetPropertyIds)int.Parse(mainParts[1]);
96+
97+
// READ operation
98+
if (item.FormData.Bacv_useService.Trim().ToLower() == "readproperty")
99+
{
100+
try
101+
{
102+
IList<BacnetValue> values_r1 = new List<BacnetValue>();
103+
bool result_r1 = Client.ReadPropertyRequest(deviceAddress, objectId, propertyId, out values_r1);
104+
if (result_r1 && values_r1.Count > 0 && values_r1[0].Value != null)
105+
{
106+
float val_r1 = (float)values_r1[0].Value;
107+
item.Value = val_r1.ToString("R", CultureInfo.InvariantCulture);
108+
NotifyOutputItems(item, item.Value);
109+
res = 1;
110+
}
111+
}
112+
catch (Exception)
113+
{
114+
return res;
115+
}
116+
}
117+
}
118+
catch (Exception)
119+
{
120+
return res;
121+
}
122+
return res;
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)