Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 102 additions & 10 deletions src/AasxOpcUa2Client/AasOpcUaClient2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,19 +59,32 @@ 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;
_userName = userName;
_password = password;
_timeOutMs = timeOutMs;
_log = log;
_securityMode = (MessageSecurityMode)securityMode;
_securityPolicy = securityPolicy;
_autoConnect = (bool)autoConnect;
}

public async Task DirectConnect()
Expand Down Expand Up @@ -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,
};

///<summary>
///Set up auto connection by getting all endpoints from server and using one of these endpoints
///Used by AID Submodel
///</summary>
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;
}
}

}

///<summary>
///Set up Secure Session. Used by AID Submodel
///</summary>

else if (_securityMode != MessageSecurityMode.None && _securityPolicy != SecurityPolicyUris.None)
{
///<summary>
///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
///</summary>
_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
);

}
///<summary>
/// Set up Unsecure Session. Used by AID, MTP and UaClient plugins
///</summary>

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");
Expand Down
3 changes: 0 additions & 3 deletions src/AasxPackageExplorer.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<None Remove="Resources\logo-modbus.png" />
<None Remove="Resources\logo-mqtt.png" />
<None Remove="Resources\logo-opc-ua.png" />
<None Remove="Resources\logo-bacnet.png" />
</ItemGroup>
<ItemGroup>
<None Update="AasxPluginAssetInterfaceDesc.options.json">
Expand All @@ -33,6 +34,7 @@
<EmbeddedResource Include="Resources\logo-modbus.png" />
<EmbeddedResource Include="Resources\logo-mqtt.png" />
<EmbeddedResource Include="Resources\logo-opc-ua.png" />
<EmbeddedResource Include="Resources\logo-bacnet.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AasxIntegrationBaseGdi\AasxIntegrationBaseGdi.csproj" />
Expand All @@ -42,6 +44,7 @@
<ProjectReference Include="..\AasxPredefinedConcepts\AasxPredefinedConcepts.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BACnet" Version="3.0.1-beta6" />
<PackageReference Include="FluentModbus" Version="5.0.3" />
<PackageReference Include="MQTTnet" Version="4.1.1.318" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
{
"Records": [
{
"IsDescription": true,
"IsDescription1_0": true,
"IsDescription1_1": false,
"IsMapping": false,
"AllowSubmodelSemanticId": [
{
"type": "Submodel",
"value": "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Submodel"
}
],
"UseHttp": false,
"UseHttp": true,
"UseModbus": false,
"UseMqtt": false,
"UseOpcUa": 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": [
{
Expand Down
125 changes: 125 additions & 0 deletions src/AasxPluginAssetInterfaceDesc/AidBacnetConnection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using AdminShellNS;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO.BACnet;
using System.Threading.Tasks;
using Aas = AasCore.Aas3_0;

namespace AasxPluginAssetInterfaceDescription
{
public class AidBacnetConnection : AidBaseConnection
{
public BacnetClient Client;
private Dictionary<uint, BacnetAddress> DeviceAddresses = new Dictionary<uint, BacnetAddress>();
public BacnetAddress deviceAddress;

override public async Task<bool> Open()
{
try
{
Client = new BacnetClient();
Client.OnIam += OnIamHandler;

if (TimeOutMs >= 10)
{
Client.Timeout = (int)TimeOutMs;
}

Client.Start();

// 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)
{
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()
{
return Client != null;
}

override public void Close()
{
// Dispose client
if (Client != null)
{
Client.Dispose();
Client = null;
}
}

override public int UpdateItemValue(AidIfxItemStatus item)
{
int res = 0;
if (item?.FormData?.Href?.HasContent() != true ||
item.FormData.Bacv_useService?.HasContent() != true ||
!IsConnected() ||
Client == null)
{
return res;
}
try
{

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);

var propertyId = (BacnetPropertyIds)int.Parse(mainParts[1]);

// READ operation
if (item.FormData.Bacv_useService.Trim().ToLower() == "readproperty")
{
try
{
IList<BacnetValue> values_r1 = new List<BacnetValue>();
bool result_r1 = Client.ReadPropertyRequest(deviceAddress, objectId, propertyId, out values_r1);
if (result_r1 && values_r1.Count > 0 && values_r1[0].Value != null)
{
float val_r1 = (float)values_r1[0].Value;
item.Value = val_r1.ToString("R", CultureInfo.InvariantCulture);
NotifyOutputItems(item, item.Value);
res = 1;
}
}
catch (Exception)
{
return res;
}
}
}
catch (Exception)
{
return res;
}
return res;
}
}
}
Loading
Loading