diff --git a/src/AasxCsharpLibrary/AdminShellCollections.cs b/src/AasxCsharpLibrary/AdminShellCollections.cs index 4e83c016..b93c0f03 100644 --- a/src/AasxCsharpLibrary/AdminShellCollections.cs +++ b/src/AasxCsharpLibrary/AdminShellCollections.cs @@ -49,7 +49,7 @@ public void Remove(K key) public List this[K key] => dict[key]; - public IEnumerable> Values + public IEnumerable> ValueLists { get { @@ -57,7 +57,17 @@ public IEnumerable> Values } } - public IEnumerable Keys + public IEnumerable Values + { + get + { + foreach (var vl in dict.Values) + foreach (var v in vl) + yield return v; + } + } + + public IEnumerable Keys { get { diff --git a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj index 893465f2..b72c7a71 100644 --- a/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj +++ b/src/AasxPluginAssetInterfaceDesc/AasxPluginAssetInterfaceDesc.csproj @@ -46,7 +46,7 @@ - + diff --git a/src/AasxPluginAssetInterfaceDesc/AidHttpConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidHttpConnection.cs index 8b82f2ef..ca419da4 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidHttpConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidHttpConnection.cs @@ -49,13 +49,14 @@ override public void Close() // nothing to do, this simple http connection is stateless } - override public void UpdateItemValue(AidIfxItemStatus item) + override public int UpdateItemValue(AidIfxItemStatus item) { // access if (item?.FormData?.Href?.HasContent() != true || item.FormData.Htv_methodName?.HasContent() != true || !IsConnected()) - return; + return 0; + int res = 0; // GET? if (item.FormData.Htv_methodName.Trim().ToLower() == "get") @@ -77,12 +78,15 @@ override public void UpdateItemValue(AidIfxItemStatus item) task2.Wait(); var strval = task2.Result; item.Value = strval; + res = 1; } } catch (Exception ex) { ; } } + + return res; } } } diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceService.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceService.cs index be00498a..9d14e03a 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceService.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceService.cs @@ -64,7 +64,14 @@ private void DispatcherTimer_Tick(object sender, EventArgs e) // block _inDispatcherTimer = true; - // .. + // call cyclic tasks + try + { + _allInterfaceStatus.UpdateValuesContinousByTick(); + } catch (Exception ex) + { + ; + } // release mutex _inDispatcherTimer = false; diff --git a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs index a5a0e9fa..d3764a73 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidInterfaceStatus.cs @@ -23,6 +23,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Net; using System.Text.RegularExpressions; using System.Globalization; +using AnyUi; namespace AasxPluginAssetInterfaceDescription { @@ -61,6 +62,11 @@ public class AidIfxItemStatus /// Link to entity (property, action, event). /// public object Tag = null; + + /// + /// Holds reference to the AnyUI element showing the value. + /// + public AnyUiUIElement RenderedUiElement = null; } public enum AidInterfaceTechnology { HTTP, Modbus, MQTT } @@ -86,7 +92,8 @@ public class AidInterfaceStatus /// /// The information items (properties, actions, events) /// - public List Items = new List(); + public MultiValueDictionary Items = + new MultiValueDictionary(); /// /// Base connect information. @@ -96,7 +103,7 @@ public class AidInterfaceStatus /// /// Actual summary of the status of the interface. /// - public string LogLine = ""; + public string LogLine = "Idle."; /// /// Black = idle, Blue = active, Red = error. @@ -112,6 +119,52 @@ public class AidInterfaceStatus /// Holds the technology connection currently used. /// public AidBaseConnection Connection = null; + + /// + /// Will get increment, when a value changed. + /// + public UInt64 ValueChanges = 0; + + protected string ComputeKey(string key) + { + if (key != null) + { + if (Technology == AidInterfaceTechnology.MQTT) + { + key = key.Trim().Trim('/').ToLower(); + } + } + return key; + } + + /// + /// Computes a technology specific key and adds item. + /// + /// + public void AddItem(AidIfxItemStatus item) + { + // acceess + if (item == null) + return; + + // compute key + var key = ComputeKey(item?.FormData?.Href); + + // now add + Items.Add(key, item); + } + + /// + /// Computes key based on technology, checks if items can be found + /// and enumerates these. + /// + public IEnumerable GetItemsFor(string key) + { + key = ComputeKey(key); + if (Items.ContainsKey(key)) + foreach (var item in Items[key]) + yield return item; + } } public class AidBaseConnection @@ -120,6 +173,8 @@ public class AidBaseConnection public DateTime LastActive = default(DateTime); + public Action MessageReceived = null; + virtual public bool Open() { return false; @@ -134,8 +189,13 @@ virtual public void Close() { } - virtual public void UpdateItemValue(AidIfxItemStatus item) + /// + /// Tris to update the value (by polling). + /// + /// Number of values changed + virtual public int UpdateItemValue(AidIfxItemStatus item) { + return 0; } virtual public void PrepareContinousRun(IEnumerable items) @@ -166,7 +226,7 @@ public T GetOrCreate(string target) /// public class AidAllInterfaceStatus { - public bool[] UseTech = { false, false, true }; + public bool[] UseTech = { true, false, false }; /// /// Will hold connections steady and continously update values, either by @@ -206,6 +266,17 @@ protected AidBaseConnection GetOrCreate(AidInterfaceTechnology tech, string endp return conn; } + /// + /// Will get increment, when a value changed. + /// + public UInt64 SumValueChanges() + { + UInt64 sum = 0; + foreach (var ifc in InterfaceStatus) + sum += ifc.ValueChanges; + return sum; + } + /// /// Will connect to each target once, get values and will disconnect again. /// @@ -240,7 +311,7 @@ public void UpdateValuesSingleShot() ifc.Connection = conn; // go thru all items - foreach (var item in ifc.Items) + foreach (var item in ifc.Items.Values) conn.UpdateItemValue(item); } } @@ -259,6 +330,9 @@ public void UpdateValuesSingleShot() /// public void StartContinousRun() { + // off + ContinousRun = false; + // for all foreach (var tech in AdminShellUtil.GetEnumValues()) { @@ -284,7 +358,72 @@ public void StartContinousRun() ifc.Connection = conn; // start subscriptions .. - conn.PrepareContinousRun(ifc.Items); + conn.MessageReceived = (topic, msg) => + { + foreach (var ifc2 in InterfaceStatus) + foreach (var item in ifc2.GetItemsFor(topic)) + { + // note value + item.Value = msg; + + // note value change + ifc2.ValueChanges++; + + // remember last use + if (ifc2.Connection != null) + ifc2.Connection.LastActive = DateTime.Now; + } + }; + conn.PrepareContinousRun(ifc.Items.Values); + } + } + + // now switch ON! + ContinousRun = true; + } + + /// + /// Will stop continous run and close all connections. + /// + public void StopContinousRun() + { + // off + ContinousRun = false; + + // close all connections + foreach (var ifc in InterfaceStatus) + { + if (ifc.Connection?.IsConnected() == true) + ifc.Connection.Close(); + } + } + + /// + /// In continous run, will fetch values for polling based technologies (HTTP, Modbus, ..). + /// + public void UpdateValuesContinousByTick() + { + // access allowed + if (!ContinousRun) + return; + + // for all + foreach (var tech in AdminShellUtil.GetEnumValues()) + { + // use? + if (!UseTech[(int)tech]) + continue; + + // find all interfaces with that technology + foreach (var ifc in InterfaceStatus.Where((i) => i.Technology == tech)) + { + // get a connection + if (ifc?.Connection?.IsConnected() != true) + continue; + + // go thru all items + foreach (var item in ifc.Items.Values) + ifc.ValueChanges += (UInt64) ifc.Connection.UpdateItemValue(item); } } } diff --git a/src/AasxPluginAssetInterfaceDesc/AidModbusConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidModbusConnection.cs index 0b87055d..4db2345b 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidModbusConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidModbusConnection.cs @@ -63,21 +63,22 @@ override public void Close() } } - override public void UpdateItemValue(AidIfxItemStatus item) + override public int UpdateItemValue(AidIfxItemStatus item) { // access if (item?.FormData?.Href?.HasContent() != true || item.FormData.Modbus_function?.HasContent() != true) - return; + return 0; + int res = 0; // decode address + quantity // (assumption: 1 quantity = 2 bytes) var match = Regex.Match(item.FormData.Href, @"^(\d{1,5})(\?quantity=(\d+))?$"); if (!match.Success) - return; + return 0; if (!int.TryParse(match.Groups[1].ToString(), out var address)) - return; + return 0; if (!int.TryParse(match.Groups[3].ToString(), out var quantity)) quantity = 1; quantity = Math.Max(0, Math.Min(0xffff, quantity)); @@ -94,7 +95,7 @@ override public void UpdateItemValue(AidIfxItemStatus item) // success with reading? if (id == null || id.Length < 1) - return; + return 0; // swapping (od = out data) // https://doc.iobroker.net/#de/adapters/adapterref/iobroker.modbus/README.md?wp @@ -188,6 +189,9 @@ override public void UpdateItemValue(AidIfxItemStatus item) // save in item item.Value = strval; + + // ok + return 1; } } } diff --git a/src/AasxPluginAssetInterfaceDesc/AidMqttConnection.cs b/src/AasxPluginAssetInterfaceDesc/AidMqttConnection.cs index b8c64e6a..cf82546e 100644 --- a/src/AasxPluginAssetInterfaceDesc/AidMqttConnection.cs +++ b/src/AasxPluginAssetInterfaceDesc/AidMqttConnection.cs @@ -86,12 +86,13 @@ private async Task Client_ApplicationMessageReceivedAsync(MqttApplicationMessage return; // payload? - var payload = Encoding.UTF8.GetString(arg.ApplicationMessage.PayloadSegment); + // var payload = Encoding.UTF8.GetString(arg.ApplicationMessage.PayloadSegment); + var payload = arg.ApplicationMessage.ConvertPayloadToString(); if (payload?.HasContent() != true) return; // refer further.. - ; + MessageReceived?.Invoke(topic, payload); // ok await Task.Yield(); @@ -113,10 +114,11 @@ override public void Close() } } - override public void UpdateItemValue(AidIfxItemStatus item) + override public int UpdateItemValue(AidIfxItemStatus item) { // Cannot do anything. MQTT is pure publish/ subscribe. // Unable to ask for a status value. + return 0; } override public void PrepareContinousRun(IEnumerable items) diff --git a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs index f2781a91..52e1b324 100644 --- a/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs +++ b/src/AasxPluginAssetInterfaceDesc/AssetInterfaceAnyUiControl.cs @@ -37,6 +37,7 @@ public class AssetInterfaceAnyUiControl private Aas.Submodel _submodel = null; private AssetInterfaceOptions _options = null; private PluginEventStack _eventStack = null; + private PluginSessionBase _session = null; private AnyUiStackPanel _panel = null; private AasxPluginBase _plugin = null; @@ -47,6 +48,8 @@ public class AssetInterfaceAnyUiControl protected Dictionary _dictTechnologyToBitmap = new Dictionary(); + private System.Timers.Timer _dispatcherTimer = null; + #endregion #region Members to be kept for state/ update @@ -62,11 +65,22 @@ public class AssetInterfaceAnyUiControl #region Constructors //============= - // ReSharper disable EmptyConstructor public AssetInterfaceAnyUiControl() { + _dispatcherTimer = new System.Timers.Timer(500); + _dispatcherTimer.Elapsed += DispatcherTimer_Tick; + _dispatcherTimer.Enabled = true; + _dispatcherTimer.Start(); + } + + public void Dispose() + { + if (_dispatcherTimer != null) + { + _dispatcherTimer.Stop(); + _dispatcherTimer.Dispose(); + } } - // ReSharper enable EmptyConstructor public void Start( LogInstance log, @@ -74,6 +88,7 @@ public void Start( Aas.Submodel theSubmodel, AssetInterfaceOptions theOptions, PluginEventStack eventStack, + PluginSessionBase session, AnyUiStackPanel panel, AasxPluginBase plugin, AidAllInterfaceStatus ifxStatus) @@ -84,6 +99,7 @@ public void Start( _submodel = theSubmodel; _options = theOptions; _eventStack = eventStack; + _session = session; _panel = panel; _plugin = plugin; _allInterfaceStatus = ifxStatus; @@ -115,6 +131,7 @@ public static AssetInterfaceAnyUiControl FillWithAnyUiControls( object opackage, object osm, AssetInterfaceOptions options, PluginEventStack eventStack, + PluginSessionBase session, object opanel, AasxPluginBase plugin, AidAllInterfaceStatus ifxStatus) @@ -131,7 +148,7 @@ public static AssetInterfaceAnyUiControl FillWithAnyUiControls( // factory this object var aidCntl = new AssetInterfaceAnyUiControl(); - aidCntl.Start(log, package, sm, options, eventStack, panel, plugin, ifxStatus); + aidCntl.Start(log, package, sm, options, eventStack, session, panel, plugin, ifxStatus); // return shelf return aidCntl; @@ -350,13 +367,13 @@ protected void RenderPanelInner( { try { + // command if (_allInterfaceStatus != null) { _allInterfaceStatus.StartContinousRun(); } - // trigger a complete redraw, as the regions might emit - // events or not, depending on this flag + // trigger a complete redraw return TriggerUpdate(full: true); } catch (Exception ex) @@ -375,9 +392,13 @@ protected void RenderPanelInner( { try { + // command + if (_allInterfaceStatus != null) + { + _allInterfaceStatus.StopContinousRun(); + } - // trigger a complete redraw, as the regions might emit - // events or not, depending on this flag + // trigger a complete redraw return TriggerUpdate(full: true); } catch (Exception ex) @@ -462,7 +483,7 @@ protected List PrepareAidInformation(Aas.Submodel sm) FormData = propName.Forms, Value = "???" }; - aidIfx.Items.Add(ifcItem); + aidIfx.AddItem(ifcItem); // directly recurse? if (propName?.Properties?.Property != null) @@ -527,7 +548,7 @@ protected void RenderTripleRowData( // if (ifx.Items != null) - foreach (var item in ifx.Items) + foreach (var item in ifx.Items.Values) { // normal row, 3 bordered cells grid.RowDefinitions.Add(new AnyUiRowDefinition()); @@ -546,6 +567,12 @@ protected void RenderTripleRowData( FontSize = 1.0f, FontWeight = AnyUiFontWeight.Normal }; + + if (ci == 4) + { + // remember value widget!! + item.RenderedUiElement = brd.Child; + } } rowIndex++; } @@ -572,6 +599,53 @@ protected void RenderTripleRowData( #endregion + #region Timer + //=========== + + private bool _inDispatcherTimer = false; + private UInt64 _lastValueChanges = 0; + + private void DispatcherTimer_Tick(object sender, EventArgs e) + { + // better debugging + _inDispatcherTimer = true; + var updateDisplay = false; + + // trigger update of values? + if (_allInterfaceStatus?.ContinousRun == true + && _allInterfaceStatus.SumValueChanges() != _lastValueChanges) + { + // rember new + _lastValueChanges = _allInterfaceStatus.SumValueChanges(); + + // re-render values only + foreach (var ifc in _allInterfaceStatus.InterfaceStatus) + foreach (var item in ifc.Items.Values) + if (item.RenderedUiElement is AnyUiSelectableTextBlock tb) + { + tb.Text = item.Value; + } + + // set + updateDisplay = true; + } + + // trigger update value? + if (_eventStack != null && updateDisplay) + _eventStack.PushEvent(new AasxPluginEventReturnUpdateAnyUi() + { + Session = _session, + PluginName = null, // do NOT call the plugin before rendering + Mode = AnyUiRenderMode.StatusToUi, + UseInnerGrid = true + }); + + // end debugging + _inDispatcherTimer = false; + } + + #endregion + #region Callbacks //=============== diff --git a/src/AasxPluginAssetInterfaceDesc/Plugin.cs b/src/AasxPluginAssetInterfaceDesc/Plugin.cs index df150040..f597abef 100644 --- a/src/AasxPluginAssetInterfaceDesc/Plugin.cs +++ b/src/AasxPluginAssetInterfaceDesc/Plugin.cs @@ -81,6 +81,8 @@ public class Session : PluginSessionBase enableCheckVisualExt: true, enableOptions: true, enableLicenses: true, + enableEventsGet: true, + enableEventReturn: true, enablePanelAnyUi: true); return res.ToArray(); } @@ -128,7 +130,7 @@ public class Session : PluginSessionBase // create session and call var session = _sessions.CreateNewSession(args[4]); session.AnyUiControl = AasxPluginAssetInterfaceDescription.AssetInterfaceAnyUiControl.FillWithAnyUiControls( - _log, args[0], args[1], _options, _eventStack, args[2], this, _allInterfaceStatus); + _log, args[0], args[1], _options, _eventStack, session, args[2], this, _allInterfaceStatus); // give object back var res = new AasxPluginResultBaseObject();