Tag Subscription |
The following code is an example of how the DataProvider can be used to subscribe to live data to calculate the average of two or more source tags.
Screen script used to calculate average of live data.
using AxiomCore2; using AxiomCore2.Client; using AxiomCore2.ControlProperties; using AxiomCore2.Controls; using AxiomCore2.Data; using AxiomCore2.Events; using AxiomCore2.Legacy; using AxiomCore2.Log; using AxiomCore2.Managers; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Collections; using System.Drawing; namespace AxiomScript { public partial class ScreenScript : IDisposable { // custom script class we are using to calculate the average of an array of source tags private AverageSubscription _averageSubscription; // NOTE: click help icon in dialog header for API documentation and examples. // NOTE: use EventTimer class when timer is necessary. See API documentation. // default resource references public ControlApplication Application { get; } = ControlApplication.Instance; public ControlFactoryManager ControlFactory { get; } = ControlFactoryManager.Instance; public IDataProvider DataProvider { get; } = DataProviderManager.CreateInstance(); public ILog Log { get; } = ClientLog.UserScript; public NavigationManager Navigation { get; } = NavigationManager.Instance; public ControlScreen Screen => _screen; public void OnScreenVisible() { // create our AverageSubscription class that will start calculating the average // of the source tag array we pass to the class constructor. Register our OnAverageChange // method to be executed any time the average changes. _averageSubscription = new AverageSubscription( dataProvider: DataProvider, log: Log, onAverageChange: OnAverageChange, sourceTags: new string[] { "<server>view.tag 1", // fully qualified source tag "<server>view.tag 2" // fully qualified source tag }); } public void OnScreenInvisible() { // stop listening for average changes _averageSubscription.Dispose(); } public void Dispose() { // cleanup _averageSubscription.Dispose(); } // we can register this method to be used by the AverageSubscription class to // get invoked any time our average changes private void OnAverageChange(double average) { // assuming we have a label control on the current screen named "Label1" ControlLabel label1 = Screen.ScreenControls["Label1"] as ControlLabel; string text = "Average: " + average.ToString("0.00"); label1.Text = text; // update text displayed in label } } // our custom class that we will use to manage subscriptions in the data provider // and invoke the onAverageChange delegate any time our calculated average changes public class AverageSubscription : IDisposable { // reference to the IDataProvider interface private readonly IDataProvider _dataProvider; // reference to the ILog interface private readonly ILog _log; // method that we will invoke after the average of the source tags is calculated private readonly Action<double> _onAverageChange; // source tags we will be using to calculate an average from private readonly string[] _sourceTags; // subscription keys we will need to unregister when cleaning up private List<string> _subscriptionKeys; // object used to store the current value of each source tag so we can calculate our average private Dictionary<string, TVQ> _tvqs; // constructor used when creating our AverageSubscription class public AverageSubscription( IDataProvider dataProvider, ILog log, Action<double> onAverageChange, string[] sourceTags) { _dataProvider = dataProvider; _log = log; _onAverageChange = onAverageChange; _sourceTags = sourceTags; _subscriptionKeys = new List<string>(); _tvqs = new Dictionary<string, TVQ>(); // start getting values of source tags and calculating their averages Subscribe(); } // method that can be called when we should stop listening for updates public void Dispose() { Unsubscribe(); } // start listening for source tag changes private void Subscribe() { foreach (string sourceTag in _sourceTags) { string subscriptionKey; // key that we can use to later unsubscribe from value changes var subscriptionArgs = new TagSubscriptionArgs(sourceTag); // register the OnSubscriptionUpdate method to get invoked any time // the source tag value changes and also on startup when we are getting // the current value var subscriptionResult = _dataProvider.SubscribeTag( subscriptionArgs, OnSubscriptionUpdate, out subscriptionKey); if (subscriptionResult.IsSuccess) { // remember subscription key so we can unsubscribe later _subscriptionKeys.Add(subscriptionKey); // default value of the source tag until our OnSubscriptionUpdate // delegate is invoked and we can set the live data value _tvqs[sourceTag] = null; } if (subscriptionResult.HasError) { _log.Warn($"Failed to subscribe tag {sourceTag}. ERROR: {subscriptionResult.Error}"); } } } // stop listening for value changes from the source tags private void Unsubscribe() { foreach (string subscriptionKey in _subscriptionKeys) { // unregister the OnSubscriptionUpdate method for the subscription key _dataProvider.UnsubscribeTag(subscriptionKey, OnSubscriptionUpdate); } } // method that we can register to get called any time a source tag value changes // or on startup when we are getting the current value private void OnSubscriptionUpdate(TagSubscriptionArgs args, TVQ tvq) { // get the source tag that is changing string sourceTag = args.SourceTag; // store the current value of the source so we can use it in our calculation _tvqs[sourceTag] = tvq; // calculate average and invoke the onAverageChange delegate that was // passed to our class in the constructor UpdateAverage(); } // calculate average of all source tags and invoke onAverageChange delegate private void UpdateAverage() { double sum = 0; foreach (TVQ tvq in _tvqs.Values) { if (tvq == null) continue; // tvq will be null when first initializing double number; if (double.TryParse(tvq?.Value.ToString() ?? "NaN", out number) && !double.IsNaN(number)) sum += number; else { string value = tvq?.Value?.ToString() ?? "null"; _log.Info($"Failed to parse '{value}'"); _onAverageChange(double.NaN); return; } } // invoke the onAverageChange delegate that was // passed to our class in the constructor double average = sum / _tvqs.Count; _onAverageChange(average); } } }