Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Abstract Nonsense in Software Development. Real-time

4.90/5 (35 votes)
7 Jan 2014CPOL17 min read 49.4K  
Application of abstract approach to real-time.

Abstract nonsense

Useful links

1 Introduction.

This article contains further development of my previous article. Abstract nonsense (Category Theory) in mathematics is a common language for different branches of math which can be regarded as code reuse. Similarly number of my program components is being reduced by the time with simultaneous increasing of functionality. During 2007 - 2010 I developed SCADA systems. I find that OPC Foundation is an excellent real-time technology. However a lot of AeroSpace companies does not understand this fact. I lot of people think that realtime control of an industrial enterprise is principally different from AeroSpace control. In result some companies use sockets instead OPC Foundation. This article contains a unified approach to real-time with examples of industrial control and AeroSpace. Recently I visited a seminar devoted to Simulink applications. The lector said following.

++++++++++

If you would like to develop a control system then our company shall help you.

++++++++++

It means that Simulink is quite clear for developers of Simulink only. The Simulink is very powerfull product with huge facilities. However this circumstance makes Simulink unvailable for a lot of people. I think that number Simulink components can be reduced such that Simulink shall support previous functionally but become more clear and available. I would like make more clear and available my own soft. I find that SCADA systems have relatively small number of components. However small number of components supplies a very reach functionality.

Laurence Chisholm Young wrote.

+++++++++++++++++++++

The view is sometimes expressed, and by quite eminent mathematicians, that the value and interest to be attached to piece of mathematics is directly related to amount of really hard work that goes into it. This is undoubtedly very true, but it must be borne in mind that this hard work is best done behind the scenes, and that much of it goes into putting the material in a form which is easily grasped. If a reader attaches value only to what he finds hard to follow he is liable to reach a very distorted picture of mathematics today. Similarly a pioneer in motoring may have judged of the power of the car by the amount of noise and dust that it raised, but this is hardly apt in our time. In mathematics, those whose criterion of the value of a result based on weighing the pages in which it is proved, or on measuring the time needed to grasp the proof with a stop-watch have experienced a number of shocks in modern times, as simple proofs were found for that that previously required long and involved arguments. The truth is that mathematics is really like a work of art: it requires infinite pains, but final result shows nothing of them, nothing to distract the viewer from deep understanding and insight that it brings to him.

+++++++++++++++++++++

Similarly I would like to develop "light" version of the Simulink.

2 Background

I find that OPC Foundation is an excellent an excellent real-time technology. However OPC Foundation is a commercial product and I cannot share it as OpenSource. Any developer can integrate OPC Foundation into my soft. To make my soft royalty free I developed proxy of the OPC Foundation. As well as OPC Foundation my proxy contains following ingredients:

  • (Remote) events
  • (Remote) data flow

The abstract nonsense pattern is used for real time. Following picture explains application of this pattern to real-time.

Event + event handler

The Event object raises an event and the Event handler handles it. This example is empty. Following movie is not empty and some qualitative SCADA facilites of (events, data indication).

SCADA demoDigital indicator

The Input object generates to time-dependent functions.

Input

The Event object raises events. The Event handler object handles event, i.e. it takes information from Input and indicates it.

The Real-time domain contains following base types.

  • IEvent this interface is implemented by any object which raises events.
  • IEventHandler this interface is implemented by any event handler.
  • EventLink link between event and event handler. Source (resp. target) of this link should implement IEvent (resp. IEventHandler interface).

There is many to many relationship between events and their handlers. Following code contains these base types.

 /// <summary>
/// Event
/// </summary>
public interface IEvent
{
    /// <summary>
    /// Raised event
    /// </summary>
    event Action Event;

    /// <summary>
    /// Is enabled sign
    /// </summary>
    bool IsEnabled
    {
        get;
        set;
    }
}

 /// <summary>
/// Event handler
/// </summary>
public interface IEventHandler
{
    /// <summary>
    /// Adds event
    /// </summary>
    /// <param name="ev">The event to add</param>
    void Add(IEvent ev);

    /// <summary>
    /// Removes event
    /// </summary>
    /// <param name="ev">The event to remove</param>
    void Remove(IEvent ev);

    /// <summary>
    /// The On Add event
    /// </summary>
    event Action<IEvent> OnAdd;

    /// <summary>
    /// The On Remove event
    /// </summary>
    event Action<IEvent> OnRemove;

 }

 /// <summary>
/// Link between event and its handler
/// </summary>
[Serializable()]
public class EventLink : CategoryArrow, IRemovableObject
{
    #region Fields

    IEvent target;

    IEventHandler source;

    #endregion

    #region Ctor

    /// <summary>
    /// Default constructor
    /// </summary>
    public EventLink()
    {
    }

            /// <summary>
    /// Deserialization constructor
    /// </summary>
    /// <param name="info">Serialization info</param>
    /// <param name="context">Streaming context</param>
    protected EventLink(SerializationInfo info, StreamingContext conext)
    {
    }


    #endregion

    #region Overriden Members

    /// <summary>
    /// The source of this arrow
    /// </summary>
    public override ICategoryObject Source
    {
        get
        {
            return source as ICategoryObject;
        }
        set
        {
            source = value.GetSource<IEventHandler>();
        }
    }

    /// <summary>
    /// The target of this arrow
    /// </summary>
    public override ICategoryObject Target
    {
        get
        {
            return target as ICategoryObject;
        }
        set
        {
            target = value.GetTarget<IEvent>();
            source.Add(target);
        }
    }

    #endregion

    #region IRemovableObject Members

    void IRemovableObject.RemoveObject()
    {
        if ((source == null) | (target == null))
        {
            return;
        }
        source.Remove(target);
        source = null;
    }

    #endregion
}
Following text contains examples of this paradigm application.

3 Elementary Events

3.1 Forced event

A lot of engineering systems requires human-machine interface. The ForcedEvent is designed for such facility. Following code explains business logic of this class.

 /// <summary>
/// Forced event
/// </summary>
[Serializable()]
public class ForcedEvent : CategoryObject, ISerializable, IEvent
{
    #region Fields
     //....
     event Action ev = () => { };

    #endregion


    #region IEvent Members

    event Action IEvent.Event
    {
        add { ev += value; }
        remove { ev -= value; }
    }

    //...

    #endregion

    #region Public Members

    /// <summary>
    /// Forces event
    /// </summary>
    public void Force()
    {
        ev();
    }

    #endregion

The Force method raises event. Following movie explains how does forced event works.

Forced event

3.2 Timer

Timer is an object such that raises distinguished by fixed interval events. .NET Framework supports several timers. The System.Windows.Forms.Timer is intended for System.Windows.Forms applications, the System.Windows.Threading.DispatcherTimer. Universal software should support any type of timers. Following two interfaces are developed for this purpose:

/// <summary>
/// Timer event
/// </summary>
public interface ITimer  : IEvent
{
    /// <summary>
    /// Time span
    /// </summary>
    TimeSpan TimeSpan
    {
        get;
        set;
    }
}
/// <summary>
/// Timer factory
/// </summary>
public interface ITimerFactory
{
    /// <summary>
    /// New timer
    /// </summary>
    ITimer NewTimer
    {
        get;
    }
}

The Windows Forms implementation of these interfaces is presented below.

/// <summary>
/// Windows Forms Timer
/// </summary>
public class Timer : ITimer, IDisposable
{

    #region Fields

    event Action ev = () => { };

    System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();

    bool isEnabled = false;

    TimeSpan timeSpan = new TimeSpan();

    #endregion

    #region Ctor

    internal Timer()
    {
        timer.Tick += (object sender, EventArgs e) =>
        {
            ev();
        };
    }


    #endregion

    #region ITimer Members

    TimeSpan ITimer.TimeSpan
    {
        get
        {
            return timeSpan;
        }
        set
        {
            timeSpan = value;
            if (value.TotalMilliseconds <= 0)
            {
                timer.Interval = 1;
                return;
            }
            timer.Interval = (int)value.TotalMilliseconds;
        }
    }

    #endregion

    #region IEvent Members

    event Action Interfaces.IEvent.Event
    {
        add { ev += value; }
        remove { ev -= value; }
    }

    bool Interfaces.IEvent.IsEnabled
    {
        get
        {
            return isEnabled;
        }
        set
        {
            if (value == isEnabled)
            {
                return;
            }
            isEnabled = value;
            timer.Enabled = value;
            if (value)
            {
                timer.Start();
            }
            else
            {
                timer.Stop();
            }
        }
    }

    #endregion

    #region IDisposable Members

    void IDisposable.Dispose()
    {
        timer.Dispose();
    }

    #endregion

}
/// <summary>
/// Factory of windows timers
/// </summary>
public class WindowsTimerFactory : ITimerFactory
{
    #region Fields

    /// <summary>
    /// Singleton
    /// </summary>
    public static WindowsTimerFactory Singleton = new WindowsTimerFactory();

    #endregion

    #region Ctor


    private WindowsTimerFactory()
    {
    }

    #endregion

    #region ITimerFactory Members

    ITimer ITimerFactory.NewTimer
    {
        get { return new Timer(); }
    }

    #endregion
}

3.3 "Direct sum" of events

3.3.1 Category Theory analogy

The abstract nonsense (Category Theory) in math supplies unified approach to different problems. This approach is very fruitful. Category theory promotes better understanding between mathematicians as well as design patterns in software development. For example Category Theory operates with abstract direct product which is presented below.

Direct product

Above diagram means that there is a unique dashed arrow which makes above diagram commutative. If we have a category of sets, A (resp. B) is a fininte set on m (resp. n) elements then A×B is a set of mn elements. However in case of linear spaces if A is 1D space and B is a 2D space then A×B is a 3D space. Notion of the direct sum is obtained by inversion of arrows.

Direct sum

If we have a category of sets, A (resp. B) is a fininte set on m (resp. n) elements then A+B is a set of m + n elements. Furthermore, these categorical definitions give a new insight into our understanding of very first mathematical concepts, such as multiplication and addition of natural numbers, intersection, product, and union of sets, and conjunction and disjunction in mathematical logic. In particular they make addition dual to multiplication and make disjoint union more natural than the ordinary one. In simple words, everyone knows that, say,

a + b = b + a and ab = ba (for natural a and b),

but only category theory tells us that these equalities are special cases of a single result! (Source of above citation)

Let us consider the "Direct sum" in 3D Graphics. Following two pictires represent complilcated motion of helicoper

Helicopter rotor

Blade motion

So above helicopter contains 7 3D shapes (Fuselage, tail rotor and 5 blades of main rotor). The "Direct sum" of these shapes is whole helicopter as it is presented below.

Helicopter

The L 1 - L 5, Fuselage, Tail rotor are 3D shapes of blades 1-5, Fuselage, and Tail rotor respectively. The Full helicopter 3D is a "direct sum" of these shapes

Helicopter container

since it is connected to other shapes by Visible link link. Otherwise five virtual cameras are linked to Full helicopter 3D by 5 arrows. If we connect 5 cameras with 7 3D shapes we need 5 * 7 =35 arrows. In result we have 7 + 5 = 12 arrows instead 5 * 7 = 35 ones.

3.3.2 "Direct sum" of events

As well as 3D graphics events also have direct sums. Following picture contains two elementary events Forced and Timer.

Simple control system

The Event collection object is a "direct sum" of these elementary events. The Event collection is an object of EventCollection class. Following code explains how does it works

 #region IEvent Members

event Action IEvent.Event
{
    add
    {
        foreach (IEvent e in ev)
        {
            e.Event += value;
        }
    }
    remove
    {
        foreach (IEvent e in ev)
        {
            e.Event -= value;
        }
    }
}

bool IEvent.IsEnabled
{
    get
    {
        return isEnabled;
    }
    set
    {
        if (isEnabled == value)
        {
            return;
        }
        foreach (IEvent e in ev)
        {
            e.IsEnabled = value;
        }
        isEnabled = value;
    }
}

#endregion

4 Events + Information flow

Any real-time system supports information flow. Some objects of real-time are simultaneously events and sources of information. For example any OPC Subscription provides data end raises an ItemChanged event (See here). My framework also supports information flow. Here we consider objects which are elements of data flow and events.

4.1 Forced data event

4.1.1 Outlook

This object is intended for Human-Machine Interface. This object can be used in SCADA systems as well as in aviation simulators. The forced event data corresponds to the ForcedEventData class which implements both IEvent and IMeasurements. So it raises events and it is a source of information. Following code explains logics of this class.

/// <summary>
  /// Forced event data
  /// </summary>
  [Serializable()]
  public class ForcedEventData : CategoryObject, ISerializable, IMeasurements,
      IEvent, IAlias, IStarted
  {
      #region Fields


      /// <summary>
      /// Data
      /// </summary>
      object[] data;

      object[] initial;

      event Action ev = () => { };

      Action force = () => { };

      //...

      #endregion

      #region IEvent Members

      event Action IEvent.Event
      {
          add { ev += value; }
          remove { ev -= value; }
      }

      bool IEvent.IsEnabled
      {
          get
          {
              return isEnabled;
          }
          set
          {
              if (isEnabled == value)
              {
                  return;
              }
              isEnabled = value;
              force = value ? ev : () => { };
          }
      }

      #endregion


      #region Public Members

      /// <summary>
      /// Data
      /// </summary>
      public object[] Data
      {
          get
          {
              return data;
          }
          set
          {
              data = value;
              force();
          }
      }

      //...

      #endregion
     //...

Above code means that changing of data raises an event. The forced event has following user interface.

Forced eventForced eventForced event

This inteface enables us to set number of data elements and their types. Also this interface enables us chande initial and current values of data.

4.1.2 Application to control system

Let us consider the control system described by following equation.

Control system equation

where

  • x - is an actual value of the physical parameter;
  • y - is a required value of the physical parameter;
  • c - is a constant;

Required value of the physical parameter is set by human. Following picture accomplished by video explains this phenomenon.

Simple control system

The Forced is an object of the ForcedEventData type. Human uses this object for entering required value. The Timer is a timer event generator. The Event collection is a "direct sum" of Timer and Forced events. The Equation contains a differential equation of the control system. Properties of the Equation are presented below.

Differential equationDifferential equation

4.1.4 User interface

Above user interface is useful for design, but it is not convenient for working. Following user interface is appropriate for work.

Simple control system UI

Problems of user interface are explained below.

4.2 Remote events

4.2.1 Clients and Severs

Any advanced real-time system should support remote events. I have used the "A simplified non topic based Event Notification Server in wcf with multiple protocol support" as a prototype of remote events. Remote objects contains servers and clients. Servers raise events and supply information. Clients handle events and receive information. Following state chart diagram explains client server interpretability

Client server interpretability

The Register method has following signature.

/// <summary>
/// This is the Service contract for Subscription Service
/// </summary>
[ServiceContract(CallbackContract = typeof(IEvent))]
public interface IRegistration
{
    /// <summary>
    /// Registers itself
    /// </summary>
    /// <returns>Values of output names and types</returns>
    [OperationContract]
    string[] Register();

    /// <summary>
    /// Unregisters itself
    /// </summary>
    [OperationContract]
    void UnRegister();
}

The Register method returns array of strings. Odd elements of this array are names of parameters, even elements are corresponding type names. This remote method is called by client at initialization time. The sever calls the OnEvent remote method.

/// <summary>
/// This is the service contract of Publish Service
/// </summary>
[ServiceContract]
interface IEvent
{
    /// <summary>
    /// Event handler
    /// </summary>
    /// <param name="data">Alert data</param>
    [OperationContract(IsOneWay = true)]
    void OnEvent(AlertData data);
}

         /// <summary>
/// Alert data
/// </summary>
[DataContract]
[KnownType(typeof(object[]))]
public class AlertData
{
    /// <summary>
    /// Data
    /// </summary>
    [DataMember]
    public object[] Data { get; set; }
}

Above code means that server transfers to client an array of object. Logic of this element is close to OPC server group.

Following picture shows example of server

Simple serverSimple server connection

Above picture means that output parameters of the Server are Formula_1 and Formula_2 of data Data, Server uses a IPC connection, and URL of the connection is "net.pipe://localhost/Data". Boolean parameter Formula_3 of Data is an event condition, i.e. an event is raised if and only if this parameter equals to true. Properties of Data are presented below:

Simple server data

The data contains three output parameters.

NNameType
1Formula_1double
2Formula_2string
3Formula_3bool

First and second parameters are exported by Server, third one is an event condition. Exported data is used by client as it is shown below.

Simple client

Parameters of Client communication coincide with server ones. The Client preforms two functions:

  • It provides data;
  • It raises event.

Imported parameters Formula_1 and Formula_2 are used by DataConsumer. The DataConsumer is both:

  • Consumer of data;
  • Event handler.

The DataConsumer is connected to Client by the Data arrow as a consumer of data, and it is connected by the Event arrow as an event handler.

4.2.2 Start of multiple applications

Above sample implies that server and clients run as different applicatios as it is shown below:

Multiple applications

There is interopreability of these applications. We need to start several instances of application with different files. However if files are zipped to single file, then drag and drop of single zip file to desktop automatically start multiple instances. I used the The Zip, GZip, BZip2 and Tar Implementation For .NET library for this purpose.

4.2.3 Application to temperature control

Let us consider a temperature control task. Temperature is controlled by heater which has "on" and "off" positions. This physical phenomenon is described by following ordinary equation.

Heating equation

where

  • z is a heating parameter. (z = (heater is "off") ? 0 : d;
  • a, b - positive real constant
  • s;
  • T (resp. Texternal) controlled temperature (resp. external temperature).

Following picture represents a simulator of this phenomenon

Control system sensor

The Temperature object supplies information from Hydrometeorological centre of Russia. Properties of this object are presented below.

Meteo data.

Above component supplies following parameters.

NParameter nameParameter typeCurrent value of parameter
1Atmospheric pressure, mm mer. coldouble760
2Temperature, °Cdouble-1.8
3Temperature min., °Cdouble-
4Temperature max., °Cdouble-
5CommentsstringSnow strong weak
6Wind directionstringS
7Wind speed, m/sdouble2
1The general nebulosity valuedouble10
9Horizontal visibility, kmdouble4

This object is used instead an external thermometer only. The Control Signal is a remote client which supplies heating parameter. Both Timer and Control Signal are generators of events. The Event collection is a combination of these events. The Equation object supplies necessary differential equation.

Differential equationDifferential equation

The Recursive is intended for raise an analog of OPC DataChange event, proprerties of this object are presented below.

Data change conditionData change condition

Parameter d of Recursive is threshold value, b is the event condition of the Server object. We use on–off control law which is expressed below.

On off control

Following pictrure represents controller.

Temperature controller

There is feedback between controller and simulator represented in following table.

NURLServer sideServer objectClent sideClient object
1net.pipe://localhost/SensorSimulatorSensorControllerSensor
2net.pipe://localhost/ControlSignalControllerServerSimulatorControl Signal

The Required object enables us to set required value of temperarue, it raises an OnChange event. The Turn_off_control implements control law, properties of this object are presented below.

Differential equationDifferential equation

The All events object is a combination ("direct sum") of events raised by Sensor and Required, i.e All events is raised if the required or actual value of temperature is substantionally chanded. If value of controller output is changed then the Server raises remote event and exports value of the controller output.

Besides Inter-process communication all clients and servers support TCP and/or HTTP communication. So simulator of phenomenon and controller can be installed on different computers. Following picture illustrates TCP communication.

ServerClient

ServerClient

This sample requires additonal files.

5 User Intreface

Any SCADA system contains designer of user interface, following picture represents example of such designer.

SCADA designer

However Visual Studio can be used as designer of SCADA user interface.

5.1 Windows Forms desinger

I used Open-source .NET SCADA framework as protototype. Following picture contains a user interface design for temperature control example.

TermometerTermometer properties

Above picture means that edited object is event handler of the Timer, i.e edited object changes value if and only is Timer raises event. The Output of edited object is Reqursive_x parameter of the Sensor. Similar picture Following picture contains properties of a slider.

SliderSlider properties/

Above picture means that this slider enables us input Temperature of the Required. Both termometer and slider implement following interface:

/// <summary>
 /// Consumer of SCADA
 /// </summary>
 public interface IScadaConsumer
 {
     /// <summary>
     /// Scada interface
     /// </summary>
     IScadaInterface Scada
     {
         get;
         set;
     }

     /// <summary>
     /// Is enabled
     /// </summary>
     bool IsEnabled
     {
         get;
         set;
     }
 }

where IScadaInterface is abstract SCADA

/// <summary>
 /// Scada interface
 /// </summary>
 public interface IScadaInterface
 {
     /// <summary>
     /// Inputs
     /// </summary>
     Dictionary<string, object> Inputs
     {
         get;
     }

     /// <summary>
     /// Outputs
     /// </summary>
     Dictionary<string, object> Outputs
     {
         get;
     }

     /// <summary>
     /// Constants
     /// </summary>
     Dictionary<string, object> Constants
     {
         get;
     }


     /// <summary>
     /// Events
     /// </summary>
     List<string> Events
     {
         get;
     }

     /// <summary>
     /// Gets input
     /// </summary>
     /// <param name="name">Input name</param>
     /// <returns>Input</returns>
     Action<object> GetInput(string name);

     /// <summary>
     /// Gets inputs
     /// </summary>
     /// <param name="names">Input names</param>
     /// <returns>Input names</returns>
     Action<object[]> GetInput(string[] names);

     /// <summary>
     /// Gets constant
     /// </summary>
     /// <param name="name">Constant</param>
     /// <returns>Constant</returns>
     Action<object> GetConstant(string name);



     /// <summary>
     /// Gets output
     /// </summary>
     /// <param name="name">Name</param>
     /// <returns>Output</returns>
     Func<object> GetOutput(string name);


     /// <summary>
     /// Gets outputs
     /// </summary>
     /// <param name="names">Names</param>
     /// <returns>Outputs</returns>
     Func<object[]> GetOutput(string[] names);


     /// <summary>
     /// Gets event
     /// </summary>
     /// <param name="name">Event name</param>
     /// <returns>The event</returns>
     IEvent this[string name]
     {
         get;
     }

     /// <summary>
     /// Gets object of type
     /// </summary>
     /// <typeparam name="T">Type</typeparam>
     /// <param name="name">Object name</param>
     /// <returns>The object</returns>
     T GetObject<T>(string name) where T : class;

     /// <summary>
     /// The "is enabled" sign
     /// </summary>
     bool IsEnabled
     {
         get;
         set;
     }

     /// <summary>
     /// On start event
     /// </summary>
     event Action OnStart;

     /// <summary>
     /// On Stop event
     /// </summary>
     event Action OnStop;

     /// <summary>
     /// Create XML event
     /// </summary>
     event Action<XmlDocument> OnCreateXml;

     /// <summary>
     /// Xml document
     /// </summary>
     XmlDocument XmlDocument
     {
         get;
     }

     /// <summary>
     /// Error Handler
     /// </summary>
     IErrorHandler ErrorHandler
     {
         get;
         set;
     }

     /// <summary>
     /// Refresh
     /// </summary>
     void Refresh();

     /// <summary>
     /// On refresh event
     /// </summary>
     event Action OnRefresh;

 }

Implementations of IScadaConsumer for slider and thermometer are presented below.

// Slider

#region Fields


string inputString;

Action<float> input;


#endregion

[DefaultValue("")]
[Editor(typeof(ListGridComboBox), typeof(UITypeEditor))]
[DataList("GetInputs")]
[TypeConverter(typeof(ListExpandableConverter))]
[Category("SCADA"), Description("Input name"), DisplayName("Input")]
public string Output
{
    get
    {
        return inputString;
    }
    set
    {
        inputString = value;
    }
}

#region IScadaConsumer Members

IScadaInterface IScadaConsumer.Scada
{
    get
    {
        return scada;
    }
    set
    {
        if (value == null)
        {
            return;
        }
        scada = value;
        input = GetFloatInput(scada, inputString);
    }
}

bool IScadaConsumer.IsEnabled
{
    get
    {
        return isEnabled;
    }
    set
    {
        if (isEnabled == value)
        {
            return;
        }
        isEnabled = value;
        if (value)
        {
            this.ValueChanged += Slider_ValueChanged;
        }
        else
        {
            this.ValueChanged += Slider_ValueChanged;
        }
    }
}

#endregion

#region Public Membres

/// <summary>
/// Gets float input function
/// </summary>
/// <param name="scada">The SCADA</param>
/// <param name="name">The function name</param>
/// <returns>The function</returns>
public static Action<float> GetFloatInput(this IScadaInterface scada, string name)
{
    Action<object> action = scada.GetInput(name);
    return GetFloatInput(action, scada.Inputs[name]);
}

#endregion

#region Private Members

static Action<float> GetFloatInput(Action<object> action, object type)
{
    Type t = type.DetectType();


    if (t.Equals(typeof(float)))
    {
        return (float x) => { action(x); };
    }
    else
    {
        return (float x) =>
        {
            double a = (double)x;
            action(a);
        };
    }
}

void Slider_ValueChanged(object sender, EventArgs e)
{
    input(_val);
}

#endregion
// Thermometer

#region Scada Input Fields

string eventString;

string outputString;

Func<float> output;

IScadaInterface scada;

IEvent eventObject;

bool isEnabled;

#endregion

#region Public Members

/// <summary>
/// Event string
/// </summary>
[DefaultValue("")]
[Editor(typeof(ListGridComboBox), typeof(UITypeEditor))]
[DataList("GetEvents")]
[TypeConverter(typeof(ListExpandableConverter))]
[Category("SCADA"), Description("Event name"), DisplayName("Event")]
public string Event
{
    get
    {
        return eventString;
    }
    set
    {
        eventString = value;
    }
}

/// <summary>
/// Output string
/// </summary>
[DefaultValue("")]
[Editor(typeof(ListGridComboBox), typeof(UITypeEditor))]
[DataList("GetOutputs")]
[TypeConverter(typeof(ListExpandableConverter))]
[Category("SCADA"), Description("Output name"), DisplayName("Output")]
public string Output
{
    get
    {
        return outputString;
    }
    set
    {
        outputString = value;
    }
}

#endregion

#region IScadaConsumer Members

IScadaInterface IScadaConsumer.Scada
{
    get
    {
        return scada;
    }
    set
    {
        if (value == null)
        {
            return;
        }
        scada = value;
        eventObject = scada[eventString];
        output = GetFloatOutput(scada, outputString);
    }
}

bool IScadaConsumer.IsEnabled
{
    get
    {
        return isEnabled;
    }
    set
    {
        if (isEnabled == value)
        {
            return;
        }
        isEnabled = value;
        if (value)
        {
            eventObject.Event += Set;
        }
        else
        {
            eventObject.Event -= Set;
        }
    }
}

#endregion

#region Private Members

void Set()
{
    Set(output());
}

public void Set(float val)
{
    if (base.Enabled)
    {
        float temp = _val;
        if (val > _max) val = _max;
        if (val < _min) val = _min;
        _val = val;
        if (temp != _val)
        {
            OnValueChanged();
            base.Invalidate();
            base.Update();
        }
    }
}

static Func<float> GetFloatOutput(IScadaInterface scada, string name)
{
    Func<object> f = scada.GetOutput(name);
    return GetFloatOutput(f, scada.Outputs[name]);
}

static Func<float> GetFloatOutput(Func<object> func, object type)
{
   Type t = type.DetectType();
   if (t.Equals(typeof(float)))
    {
        return () => { return (float)func(); };
    }
    else
    {
        return () =>
        {
            double a = (double)func();
            return (float)a;
        };
    }
}

#endregion

Interpretability of SCADA with Windows Forms is implemented by recursion as it is presented below.

 #region Public Members
/// <summary>
/// Recursive action of control
/// </summary>
/// <param name="control">The control</param>
/// <param name="action">The action</param>
public static void RecursiveAction(this Control control, Action<Control> action)
{
    action(control);
    foreach (Control c in control.Controls)
    {
       c.RecursiveAction(action);
    }
}

/// <summary>
/// Sets scada intrerface to control
/// </summary>
/// <param name="control">The control</param>
/// <param name="scada">The scada</param>
public static void Set(this Control control, IScadaInterface scada)
{
    control.SetPrivate(scada);
    scada.OnRefresh += () => { control.SetPrivate(scada); };
    scada.OnStart += () => { control.SetScadaEnabled(true); };
    scada.OnStop += () => { control.SetScadaEnabled(false); };
}

/// <summary>
/// Sets "is enabled" sign to control
/// </summary>
/// <param name="control">The control</param>
/// <param name="isEnabled">The is enabled sign</param>
public static void SetScadaEnabled(this Control control, bool isEnabled)
{
    control.RecursiveAction((Control c) =>
    {
        if (c is IScadaConsumer)
        {
            (c as IScadaConsumer).IsEnabled = isEnabled;
        }
    });
}

#endregion

#region Private & Internal Members

private static void SetPrivate(this Control control, IScadaInterface scada)
{
    control.RecursiveAction((Control c) =>
        {
            if (c is IScadaConsumer)
            {
                (c as IScadaConsumer).Scada = scada;
            }
        });
}

#endregion

Following picture supplied with video shows temperature control example.

On off controller

Above picture represents two applications:

  • Simulator of physical phenomenon;
  • Controller.

Start includes following two steps.

1. Start of simulator in design mode. User should start Aviation.exe, and open sensor.cfa file from ipc_control.zip 144 KB

2. Start controller application.

5.2 WPF user interface

I think that WPF technology is more close to SCADA than Windows Forms. The System.Windows.Forms implies common loop of events. However any WPF UI element has its own thread and so its own dispatcher. So we can associate different WPF UI elements with different events. Some SCADA systems do not imply user interface because they supply control without HMI. Different types of SCADA imply different types of timers. If we use WPF we should replace Windows Forms Timer by following timer.

/// <summary>
 /// WPF timer
 /// </summary>
 public class Timer : ITimer, IDisposable
 {

     #region Fields

     event Action ev = () => { };

     DispatcherTimer timer = new DispatcherTimer();

     bool isEnabled = false;

     TimeSpan timeSpan = new TimeSpan();

     #endregion

     #region Ctor

     internal Timer()
     {
         timer.Tick += (object sender, EventArgs e) =>
         {
             ev();
         };
     }


     #endregion

     #region ITimer Members

     TimeSpan ITimer.TimeSpan
     {
         get
         {
             return timeSpan;
         }
         set
         {
             timeSpan = value;
             timer.Interval = value;
         }
     }

     #endregion

     #region IEvent Members

     event Action Interfaces.IEvent.Event
     {
         add { ev += value; }
         remove { ev -= value; }
     }

     bool Interfaces.IEvent.IsEnabled
     {
         get
         {
             return isEnabled;
         }
         set
         {
             if (value == isEnabled)
             {
                 return;
             }
             isEnabled = value;
             timer.IsEnabled = value;
             if (value)
             {
                 timer.Start();
             }
             else
             {
                 timer.Stop();
             }
         }
     }

     #endregion

     #region IDisposable Members

     void IDisposable.Dispose()
     {
         try
         {
             if (timer != null)
             {
                 timer.Stop();
             }
         }
         catch (Exception)
         {

         }
         timer = null;
     }

     #endregion

 }

5.4.1 Flight simulator

Any flight simulator is in fact a SCADA system accomplished by advanced 3D graphics, audio support and special devices. I think that SCADA technologies (for example OPC) would be very useful for flight simulators. I wrote an article devoted to audio support with its application to flight simulators. Following picture explains this task.

Audio business logic

As well as temperature control example this example uses meteorological component. So this component also provides angle of wind direction. Usage of wind velocity and wind direction is twofold. First of all these parameters are used in ATIS audio message. Moreover these parameters are used in motion equations. Times of audio messages depend on simulated aircraft motion. ATIS message time depends on distance to airport. Height (resp. velocity) messages are being played in transition of height (resp. velocity) times. Here is a description of this task in details. Following picture (with video + audio) shows virtual cameras.

Aviation SCADA

Installation of audio files includes following steps.

Step 1. Unpacking audio archive to any directory.

Step 2. Setting directory of unpacked files by following way

323922/SoundsDir.png

We would like to develop user interface for this task. I used Circular gauge custom control for Silverlight 3 and WPF for indication of altitude and velocity. The CircularGaugeControl is extended by following way.

 /// <summary>
/// Represents a Circular Gauge SCADA control
/// </summary>
[TemplatePart(Name = "LayoutRoot", Type = typeof(Grid))]
[TemplatePart(Name = "Pointer", Type = typeof(Path))]
[TemplatePart(Name = "RangeIndicatorLight", Type = typeof(Ellipse))]
[TemplatePart(Name = "PointerCap", Type = typeof(Ellipse))]
public class ScadaCircularGaugeControl : CircularGauge.CircularGaugeControl, IScadaConsumer
{
    #region Fields

    #region Scada Input Fields

    Func<double> output;

    IScadaInterface scada;

    IEvent eventObject;

    bool isEnabled;

    #endregion

    #region Ctor

    public ScadaCircularGaugeControl()
    {
        Unloaded += (object sender, RoutedEventArgs e) =>
         { (this as IScadaConsumer).IsEnabled = false; };
    }

    #endregion

    #endregion

    #region IScadaConsumer Members

    IScadaInterface IScadaConsumer.Scada
    {
        get
        {
            return scada;
        }
        set
        {
            if (value == null)
            {
                return;
            }
            scada = value;
            if (eventObject != null)
            {
                if (isEnabled)
                {
                    eventObject.Event -= Set;
                }
            }
            eventObject = scada[Event];
            output = scada.GetDoubleOutput(Output);
        }
    }

    bool IScadaConsumer.IsEnabled
    {
        get
        {
            return isEnabled;
        }
        set
        {
            if (isEnabled == value)
            {
                return;
            }
            isEnabled = value;
            if (value)
            {
                eventObject.Event += Set;
            }
            else
            {
                eventObject.Event -= Set;
            }
        }
    }

    #endregion

    #region Public Members

    /// <summary>
    /// Event
    /// </summary>
    [Browsable(true)]
    [TypeConverter(typeof(EventConverter))]
    [Category("SCADA"), Description("Event name"), DisplayName("Event")]
    public string Event
    {
        get;
        set;
    }

    /// <summary>
    /// Output
    /// </summary>
    [Browsable(true)]
    [TypeConverter(typeof(OutputRealConverter))]
    [Category("SCADA"), Description("Output name"), DisplayName("Output")]
    public string Output
    {
        get;
        set;
    }

    #endregion

    #region Private Methods

    void Set()
    {
        base.CurrentValue = output();
    }

    #endregion

}

Editor of properties of this component is presented below.

Properties of circular control

Properties of this control are similar to termometer ones. WPF SCADA contains special user control (UserControlCamera) for virtual 3D camera. Propreties of this control are presented below.

Properties of 3D camera

Full application accomplished with video + audio is presented below.

Aviation WPF SCADA

The sounds directory from sounds.zip archive should be copied to the location of Scada.WPF.Sound.Sample.exe file.

5.4.2 Real-time satellite 3D vizualization

I wrote an article devoted to determination of orbits of artificial satellites. However we can extend functionality by real-time visualization. We would like use parameters of present time satellites an show different pictures of Earth from virtual satellites. Similar task is described in my previous article. However early I did not consider real-time. Now this task is accomplished by following features:

The NORAD Two-Line Element Sets web site supplies Keplerian elements for most satellites in low-earth orbit. I used HTML to XAML converter for development of the wrapper. Following pictures shows the website and its wrapper.

Web site

NORAD siteNORAD 2 lines

WPF Wrapper

WrapperWrapperWrapper

I also used Celestia. Real-time 3D visualization of space, I converted some classes from C++ to C#.  Moreover I developed object oriented wrapper of Empirical, global model of the Earth's atmosphere from ground to space (NRLMSISE-00).  In result we have following picture.

CelesTrak

The Timer is a timer for real-time. The NASA Image object is intended for textures. We can enter any URL from NASA Earth Observations web site.

NASA image

The Celestrak is a wrapper NORAD Two-Line Element Sets website.

So we have business logic, now we wold like develop user interface. We use UserControlCamera control for visualization of camera. We also use GridTextOutput and UserControlChartGroup for output of digital parameters. This components are presented below.

Digital WPF components

The UserControlWebPage is used for change texture

Texture change

The UserControlCelestrak is used for selection of the satellite.

Satellite change

In result we have following application.

CelesTrak WPF

Points of Interest

One man said to me: "I know more than you because I 50 years worked at single company." I answered: "I know more than you because I worked at lot of companies.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)