Skip to content

Commit

Permalink
* introduce update freq
Browse files Browse the repository at this point in the history
   and polling time
  • Loading branch information
festo-i40 committed Jan 13, 2024
1 parent 12be817 commit 61fcf7e
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 132 deletions.
72 changes: 14 additions & 58 deletions src/AasxPluginAssetInterfaceDesc/AasOpcUaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,76 +58,29 @@ public class AasOpcUaClient
protected static bool _autoAccept = true;
protected string _userName;
protected string _password;
protected uint _timeOutMs = 2000;

protected ISession _session;
protected SessionReconnectHandler _reconnectHandler;

public AasOpcUaClient(string endpointURL, bool autoAccept,
string userName, string password, LogInstance log = null)
string userName, string password,
uint timeOutMs = 2000,
LogInstance log = null)
{
_endpointURL = endpointURL;
_autoAccept = autoAccept;
_userName = userName;
_password = password;
_timeOutMs = timeOutMs;
_log = log;
}

private BackgroundWorker worker = null;

public void Run()
{
// start server as a worker (will start in the background)
// ReSharper disable once LocalVariableHidesMember
var worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += (s1, e1) =>
{
try
{
while (true)
{
StartClientAsync().Wait();
// keep running
if (ClientStatus == AasOpcUaClientStatus.Running)
while (true)
Thread.Sleep(200);
// restart
Thread.Sleep(200);
}
}
catch (Exception ex)
{
AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex);
}
};
worker.RunWorkerCompleted += (s1, e1) =>
{
;
};
worker.RunWorkerAsync();
}

public async Task DirectConnect()
{
await StartClientAsync();
}

public void Cancel()
{
if (worker != null && worker.IsBusy)
try
{
worker.CancelAsync();
worker.Dispose();
}
catch (Exception ex)
{
AdminShellNS.LogInternally.That.SilentlyIgnoredError(ex);
}
}

public void Close()
{
if (_session == null)
Expand Down Expand Up @@ -195,7 +148,8 @@ public async Task StartClientAsync()
_log?.Info("2 - Discover endpoints of {0}.", _endpointURL);
ClientStatus = AasOpcUaClientStatus.ErrorDiscoverEndpoints;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
var selectedEndpoint = CoreClientUtils.SelectEndpoint(_endpointURL, haveAppCertificate, 500);
var selectedEndpoint = CoreClientUtils.SelectEndpoint(_endpointURL, haveAppCertificate,
(int) _timeOutMs);
_log?.Info(" Selected endpoint uses: {0}",
selectedEndpoint.SecurityPolicyUri.Substring(selectedEndpoint.SecurityPolicyUri.LastIndexOf('#') + 1));

Expand All @@ -205,7 +159,7 @@ public async Task StartClientAsync()
var endpoint = new ConfiguredEndpoint(null, selectedEndpoint, endpointConfiguration);

_session = await Session.Create(
config, endpoint, false, "AasxPluginAssetInterfaceDesc", 2000,
config, endpoint, false, "AasxPluginAssetInterfaceDesc", _timeOutMs,
new UserIdentity(_userName, _password), null);

// register keep alive handler
Expand All @@ -224,13 +178,15 @@ private void Client_KeepAlive(Opc.Ua.Client.ISession sender, KeepAliveEventArgs
{
if (e.Status != null && ServiceResult.IsNotGood(e.Status))
{
_log?.Info("Keep alive {0} {1}/{2}", e.Status, sender.OutstandingRequestCount, sender.DefunctRequestCount);
_log?.Info("Keep alive {0} {1}/{2}", e.Status, sender.OutstandingRequestCount,
sender.DefunctRequestCount);

if (_reconnectHandler == null)
{
_log?.Info("--- RECONNECTING ---");
_reconnectHandler = new SessionReconnectHandler();
_reconnectHandler.BeginReconnect(sender, ReconnectPeriod * 1000, Client_ReconnectComplete);
_reconnectHandler.BeginReconnect(
sender, ReconnectPeriod * (int) _timeOutMs, Client_ReconnectComplete);
}
}
}
Expand Down Expand Up @@ -356,13 +312,13 @@ public DataValue ReadNodeId(NodeId nid)
}

public Opc.Ua.Client.Subscription SubscribeNodeIds(NodeId[] nids, MonitoredItemNotificationEventHandler handler,
int publishingInteral = 1000)
int publishingInterval = 1000)
{
if (_session == null || nids == null || !_session.Connected || handler == null)
return null;

var subscription = new Subscription(_session.DefaultSubscription)
{ PublishingInterval = publishingInteral };
{ PublishingInterval = publishingInterval };

foreach (var nid in nids)
{
Expand Down
168 changes: 163 additions & 5 deletions src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This source code may use other Open Source software components (see LICENSE.txt)
using System.Text.RegularExpressions;
using System.Globalization;
using AnyUi;
using System.Windows.Media.Animation;

namespace AasxPluginAssetInterfaceDescription
{
Expand Down Expand Up @@ -125,10 +126,16 @@ public class AidInterfaceStatus
public UInt64 ValueChanges = 0;

/// <summary>
/// If greater 10, specifies the time rate for polling resp. subscriptions
/// If greater 10, specifies the time rate in milli seconds for polling the
/// respective subscriptions.
/// </summary>
public double UpdateFreqMs = 0;

/// <summary>
/// If greater 10, specifies the desired timeout in milli seconds.
/// </summary>
public double TimeOutMs = 0;

/// <summary>
/// To be used with <c>UpdateFreqMs</c>.
/// </summary>
Expand Down Expand Up @@ -211,6 +218,17 @@ public class AidBaseConnection
/// </summary>
public string Password = null;

/// <summary>
/// If greater 10, specifies the time rate in milli seconds for polling the
/// respective subscriptions.
/// </summary>
public double UpdateFreqMs = 0;

/// <summary>
/// If greater 10, specifies the desired timeout in milli seconds.
/// </summary>
public double TimeOutMs = 0;

public DateTime LastActive = default(DateTime);

public Action<string, string> MessageReceived = null;
Expand Down Expand Up @@ -313,12 +331,17 @@ public AidAllInterfaceStatus(LogInstance log = null)
}

protected AidBaseConnection GetOrCreate(
AidInterfaceTechnology tech, string endpointBase,
AidInterfaceStatus ifcStatus,
string endpointBase,
LogInstance log = null)
{
// access
if (ifcStatus == null)
return null;

// find connection by factory
AidBaseConnection conn = null;
switch (tech)
switch (ifcStatus.Technology)
{
case AidInterfaceTechnology.HTTP:
conn = HttpConnections.GetOrCreate(endpointBase, log);
Expand All @@ -336,6 +359,10 @@ protected AidBaseConnection GetOrCreate(
conn = OpcUaConnections.GetOrCreate(endpointBase, log);
break;
}

conn.UpdateFreqMs = ifcStatus.UpdateFreqMs;
conn.TimeOutMs = ifcStatus.TimeOutMs;

return conn;
}

Expand Down Expand Up @@ -376,7 +403,7 @@ public void UpdateValuesSingleShot()

// find connection by factory
// single means: log the events
AidBaseConnection conn = GetOrCreate(tech, ifc.EndpointBase, _log);
AidBaseConnection conn = GetOrCreate(ifc, ifc.EndpointBase, _log);
if (conn == null)
continue;

Expand Down Expand Up @@ -447,7 +474,7 @@ public void StartContinousRun()
}

// find connection by factory
AidBaseConnection conn = GetOrCreate(tech, ifc.EndpointBase);
AidBaseConnection conn = GetOrCreate(ifc, ifc.EndpointBase);
if (conn == null)
continue;

Expand Down Expand Up @@ -548,6 +575,137 @@ await Parallel.ForEachAsync(
}
}
}

//
// Building, intake from Submodel
//

public void PrepareAidInformation(Aas.Submodel sm)
{
// access
InterfaceStatus.Clear();
if (sm == null)
return;

// get data
var data = new AasxPredefinedConcepts.AssetInterfacesDescription.CD_AssetInterfacesDescription();
PredefinedConceptsClassMapper.ParseAasElemsToObject(sm, data);

// prepare
foreach (var tech in AdminShellUtil.GetEnumValues<AidInterfaceTechnology>())
{
var ifxs = data?.InterfaceHTTP;
if (tech == AidInterfaceTechnology.Modbus) ifxs = data?.InterfaceMODBUS;
if (tech == AidInterfaceTechnology.MQTT) ifxs = data?.InterfaceMQTT;
if (tech == AidInterfaceTechnology.OPCUA) ifxs = data?.InterfaceOPCUA;
if (ifxs == null || ifxs.Count < 1)
continue;
foreach (var ifx in ifxs)
{
// new interface
var dn = AdminShellUtil.TakeFirstContent(ifx.Title, ifx.__Info__?.Referable?.IdShort);
var aidIfx = new AidInterfaceStatus()
{
Technology = tech,
DisplayName = $"{dn}",
Info = $"{ifx.EndpointMetadata?.Base}",
EndpointBase = "" + ifx.EndpointMetadata?.Base,
Tag = ifx
};
InterfaceStatus.Add(aidIfx);

// Properties .. lambda recursion
Action<string, CD_PropertyName> recurseProp = null;
recurseProp = (location, propName) =>
{
// add item
var ifcItem = new AidIfxItemStatus()
{
Kind = AidIfxItemKind.Property,
Location = location,
DisplayName = AdminShellUtil.TakeFirstContent(
propName.Title, propName.Key, propName.__Info__?.Referable?.IdShort),
FormData = propName.Forms,
Value = "???"
};
aidIfx.AddItem(ifcItem);
// directly recurse?
if (propName?.Properties?.Property != null)
foreach (var child in propName.Properties.Property)
recurseProp(location + " . " + ifcItem.DisplayName, child);
};

if (ifx.InterfaceMetadata?.Properties?.Property == null)
continue;
foreach (var propName in ifx.InterfaceMetadata?.Properties?.Property)
recurseProp("\u2302", propName);
}
}
}

protected List<int> SelectValuesToIntList<ITEM>(
IEnumerable<ITEM> items,
Func<ITEM, string> selectStringValue) where ITEM : class
{
return items
// select polling time present
.Select(selectStringValue)
.Where((pt) => pt != null)
// convert to int
.Select(s => Int32.TryParse(s, out int n) ? n : (int?)null)
.Where(n => n.HasValue)
.Select(n => n.Value)
.ToList();
}

protected void SetDoubleOnDefaultOrAvgOfIntList(
ref double theValue,
double minimumVal,
double defaultVal,
List<int> list)
{
if (defaultVal >= minimumVal)
theValue = defaultVal;
if (list.Count > 0)
theValue = Math.Max(minimumVal, list.Average());
}

/// <summary>
/// to be called after <c>PrepareAidInformation</c>
/// </summary>
public void SetAidInformationForUpdateAndTimeout(
double defaultUpdateFreqMs = 0,
double defaultTimeOutMs = 0)
{
// for Modbus, analyze update frequency and timeout
foreach (var ifc in InterfaceStatus.Where((i) => i.Technology == AidInterfaceTechnology.Modbus))
{
// polltimes
SetDoubleOnDefaultOrAvgOfIntList(
ref ifc.UpdateFreqMs, 10.0, defaultUpdateFreqMs,
SelectValuesToIntList(ifc?.Items?.Values, (it) => it.FormData?.Modbus_pollingTime));

// time out
SetDoubleOnDefaultOrAvgOfIntList(
ref ifc.TimeOutMs, 10.0, defaultTimeOutMs,
SelectValuesToIntList(ifc?.Items?.Values, (it) => it.FormData?.Modbus_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));
}
}
}

}
2 changes: 2 additions & 0 deletions src/AasxPluginAssetInterfaceDesc/AidModbusConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ override public bool Open()
try
{
Client = new ModbusTcpClient();
if (TimeOutMs >= 10)
Client.ConnectTimeout = (int)TimeOutMs;
Client.Connect(new IPEndPoint(IPAddress.Parse(TargetUri.Host), TargetUri.Port));
LastActive = DateTime.Now;
return true;
Expand Down
3 changes: 2 additions & 1 deletion src/AasxPluginAssetInterfaceDesc/AidOpcUaConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ override public bool Open()
autoAccept: true,
userName: this.User,
password: this.Password,
timeOutMs: (TimeOutMs >= 10) ? (uint) TimeOutMs : 2000,
log: Log);
// Client.Run();

Expand Down Expand Up @@ -148,7 +149,7 @@ override public void PrepareContinousRun(IEnumerable<AidIfxItemStatus> items)
var sub = Client.SubscribeNodeIds(
new[] { nid },
handler: SubscriptionHandler,
publishingInteral: 500);
publishingInterval: (UpdateFreqMs >= 10) ? (int) UpdateFreqMs : 500);
_subscriptions.Add(nodePath,
new SubscribedItem() {
NodePath = nodePath,
Expand Down
Loading

0 comments on commit 61fcf7e

Please sign in to comment.