Click or drag to resize

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.

Calculate Live Data Average

Screen script used to calculate average of live data.

C#
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); 
        }
    }
}