OPC UA Client

OAS is both an OPC UA Client and OPC UA Server.

The Open Automation Software OPC UA Server is built into the OAS Engine which can be deployed on both Windows and Linux operating systems.  OPC UA Server support was added to OAS version 14.00.0042 and does require a license of the OPC Client Connector to enable read and write functionality.

The OPC UA Server supports browsing, reading, and writing to all local and remote OAS Tags, including Live Data Cloud, provided the OAS service hosting the Tags has a license of OPC Client.

If you want interface OAS with an OPC UA Server please see the Getting Started OPC UA Server guide.

The following guide demonstrates how to interface an OPC UA Client with OAS.

Step 1 – Check OPC Client License

OAS

After installing Open Automation Software verify you have a license of OPC Client on all OAS Engines that you want to interface with.

Start Configure OAS application from the program group Open Automation Software.

Use the Configure OAS application and go to Configure-License.

Check all OAS Engines that the OPC Client product appears in the Enabled Products list.

Enabled Products

Step 2 – Define User Security

The OAS Engine by default restricts Tag Browsing so in order for an OPC UA Client to browse tags there are 2 options.

Option 1: Specify User authentication in the OPC UA Client with an OAS user that would have access to Get Tag Group Names and Get Tags Names.

OPC UA Client User

Option 2: Go to Configure-Security and select the Default security group and allow Get Tag Group Names and Get Tag Names in the Tags tab of the Default security group.

Allow Tag Browsing

Step 3 – Connect OPC UA Client

In the OPC UA Client software use the endpoint url opc.tcp://localhost:58728 to connect to the server.  You can change the port number under Configure-Options-Network of the OAS Engine where the server is hosted with the property OAS OPC UA Server Port Number.

OAS OPC UA Server Port
OAS OPC UA Server Endpoint

Connect to the server from an OPC UA Client. We recommend Unified Automation’s UA Expert if you do not have a client to test with. You can also use OAS as an OPC UA client to define tags with data source of OPC UA.

Note: If you are unable to connect to the OAS OPC UA Server due to a certificate security error that is shown in the OAS System Errors go to C:\ProgramData\OpenAutomationSoftware\pki-server\ on Windows or the pki-server sub-directory where the OAS Engine is located on Linux and copy the files in the rejected\certs directory to trusted\certs.

Step 4 – Browse OAS Tags

From the OPC Client browse either the Local service or a remote service under the Network branch.

OPC UA Server Network

To add network nodes or IP Addresses where other OAS Engines are running go to Configure-Options-Networking and add to the NETWORK NODES form.  These will appear under the Network branch. 

Network Node List

We have used 127.0.0.1 in this example to demonstrate the feature, but in actual use it is not needed as it would be the same as browsing Local.

You will be able to browse all Tag Groups and Tags from the local or remote OAS Engine to then see all property values available for a Tag.

Value is the most commonly used Variable.  See Tag Variables for a complete list of all variables possible.

OPC UA Tag Browse

The NodeId returned with be the full tag path and the property selected.  It this case Ramp.Value for the local Tag Ramp.

OPC UA NodeId

Refer to Basic Networking and Live Data Cloud Networking for how remote Tag NodeIds are returned.

Local Tag

myGroup.myTag.Value

Basic Networking

\\192.168.0.1\myGroup.myTag.Value

Live Data Cloud Networking from local OAS Engine

RemoteSCADAHosting.myLiveDataCloudNode.myGroup.myTag.Value

Live Data Cloud Networking though remote OAS Engine

\\192.168.0.1\RemoteSCADAHosting.myLiveDataCloudNode.myGroup.myTag.Value
RemotesSCADAHosting OPC UA Tag

NOTE: Live Data Cloud hosting is only required if the hosting server has a dynamic IP Address without a fixed IP Address or fixed network node name.  Basic Networking is the most common method of networking.

Continue to browse additional Tag values to add to your OPC Client specifying the Publishing Rate for your desired update rate to your Client.

All Data Sources for OAS with a valid license are supported for communications through the OAS OPC UA Server.

You can deploy the OAS Engine to multiple server PCs, each OPC UA Server support communications to any OAS Engine with an OPC Client license that is also version 14.00.0042 or greater.

Getting Started – Cross Platform HMI

If you do not have a copy of Visual Studio you can download a free version of Visual Studio Community (formerly Visual Basic Express or C# Express) from visualstudio.microsoft.com/vs/community.  You can choose whether to use Visual Basic or C#, but if you have no experience with either language, Visual Basic is easier for new developers.  We do recommend Visual Studio 2017 or higher.

There are many Cross Platform GUI tools available.  We have chosen Avalonia because of its popularity.  Here’s a link detailing how to create an Avalonia project: https://avaloniaui.net/docs/quickstart/create-new-project.  We recommend downloading and using the extension: https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio

Working Avalonia Solution

You should have a running Avalonia application at this point

Adding OASAvalonia to Existing Avalonia Solution

Step 1 – Add the OASAvalonia library

Right-Click on Dependencies in the Solution Explorer

Click the “Browse” button at the bottom

Navigate to C:\Program Files\Open Automation Software\OAS\

Click “OK” button

Step 2 – Add Namespace

Edit the Mainwindow.xaml file

Add xmlns:b=”clr-namespace:OASAvalonia;assembly=OASAvalonia” to the xml namespaces

Step 2 – Add OAS_Controls

Type “<OAS_” and you will a list of  OAS enhanced controls

Choose one and add an OAS properties – below are the properties for  OAS_TextBox:

<b:OAS_TextBox Grid.Row=”0″ Grid.Column=”2″ Name=”OAS_TextBox” Width=”70″ Height=”26″ Text_TagName=”OASAvalonia.Text_TagName.Value”
IsVisible_TagName=”OASAvalonia.IsVisible_TagName.Value” IsVisible_Inverse=”False” IsVisible_BadQualityTrue=”True” IsVisible_Inverse_Link=”PCB_IsVisible_Inverse” IsVisible_BadQualityTrue_Link=”PCB_IsVisible_BadQualityTrue”
IsEnabled_TagName=”OASAvalonia.IsEnabled_TagName.Value” IsEnabled_Inverse=”False” IsEnabled_BadQualityTrue=”True” IsEnabled_Inverse_Link=”PCB_IsEnabled_Inverse” IsEnabled_BadQualityTrue_Link=”PCB_IsEnabled_BadQualityTrue”
Foreground_TagName=”OASAvalonia.Foreground_TagName.Value” Foreground_True=”Blue” Foreground_False=”RosyBrown” Foreground_BadQualityColor=”White” Foreground_True_Link=”OAS_TextBox_ComboBox_ForegroundTrue” Foreground_False_Link=”OAS_TextBox_ComboBox_ForegroundFalse”
Background_TagName=”OASAvalonia.Background_TagName.Value” Background_True=”White” Background_False=”Green” Background_BadQualityColor=”Red” Background_True_Link=”OAS_TextBox_ComboBox_BackgroundTrue” Background_False_Link=”OAS_TextBox_ComboBox_BackgroundFalse”
/>

OAS Avalonia Visual Studio Solution

We have also provided an example OAS Avalonia solution

Step 1

Load the DemoTags  Tag configuration, the default path is C:\Program Files\Open Automation Software\OAS\DemoFiles\DemoTags.Tags.

Step 2

Start Visual Studio and select File->Open->Project/Solution : OASCrossPlatformHMIDemoApplication

Step 3

Run Application

Getting Started MTConnect

The following guide shows you the easy steps to define the MTConnect schema that can be used to read the possible data items and automatically create OAS Tags to be used with live update.

How to interface MTConnect live data into Open Automation Software.

Use the following steps to become familiar with the easy setup of MTConnect in OAS.

Step 1 – Check MTConnect License

OAS

Start Configure OAS application from the program group Open Automation Software.

 

Select Configure-License. Verify that MTConnect is one of the available Drivers in the lower left of the form.  If you do not see MTConnect available contact support@oasiot.com to update your license.

NOTE: To configure remote OAS Engines enter the IP Address or node name in the Network Node field and click on Select.

Network Node
Enabled Drivers

NOTE: You will need to be running Open Automation Software Version 14.0.0.30 or greater to support MTConnect communications.  You can download the latest version at www.openautomationsoftware.com/downloads/open-automation-software/

Step 2 – Configure MTConnect Driver

Select Configure-Drivers.

Configure Drivers

Enter a meaningful Driver Interface Name that you will refer to this physical connection when defining Tags with a MTConnect Data Source.

Device 01 Driver Interface Name

Set the Driver to MTConnect.

MTConnect Driver

Leave Enable and Add Tags Automatically enabled.

Add Tags Automatically

Specify the Live Data Url for the MTConnect stream.

MTConnect Driver Interface

Optionally define a secondary failover of data stream if the primary data stream fails with the property Enable Failover.


If both the primary and secondary device are offline the Return to Online settings determines the retry frequency.

View Driver Interface Failover for more information and and video demonstrating communications failover.

Select the Add Driver button in the left window to add the Driver Interface as an available selection when defining Tags in the next step.

Add Driver

Note: If you need to define several Driver Interfaces you can use the CSV Export and CSV Import on the toolbar in the upper right together with Microsoft Excel.

CSV Import and Export

Drivers can also be programmatically assigned with the OAS REST API or .NET Server Configuration interface.

Step 3 – Configure MTConnect Tags

Select Configure-Tags.

Menu Configure Tags

With the OAS Engine in Runtime and the Driver Interface property Add Tags Automatically is enabled all of the possible MTConnect data items will be added under a Tag Group with the same name as the Driver Interface specified.

MTConnect Tags

Expand the tag group and sub groups to see all tags added.

MTConnect Tag List

If the Tag MTConnect Data Type is set to DataItem and the data stream has sent a value for the Id the current value will appear with Good Quality.

MTConnect Tag

NOTE: You can optionally enable transaction logging for MTConnect communications to record all value changes to text files.

To record all data values for all Ids received from the data stream select Configure-Options and click the Network Node Select to then view the System Logging tab and enable the property Log MTConnect Communications and set the Transaction Log Path of where the files will be placed.

MTConnect Transaction Logging

You can change the tag names of the OAS Tags if the automated creation does not suite your desired tag name.

Use the CSV Export and CSV Import on the toolbar in the upper right together with Microsoft Excel. to change multiple Tags.

CSV Import and Export

NOTE: Tags can also be programmatically assigned with the OAS REST API or .NET Server Configuration interface.

Step 4 – Save MTConnect Tag and Driver Configuration

Select the Save button on the toolbar at the top.

Load and Save

Enter a file name to be saved in C:\ProgramData\OpenAutomationSoftware\ConfigFiles directory on Windows or ConfigFiles subdirectory on Linux.

When prompted to set the file as the default configuration to load on startup select Yes.

Set Default Tag File

NOTE: The tags and drivers are both saved in to the one .Tags file.

The tags and drivers defined are now ready for use in all OAS features like Data Logging, Data Route, and Open UIEngine.

Getting Started Azure IoT Data Hub

Create an IoT hub

View the following video for a complete demonstration of how to send live data to Azure IoT Data Hub.

You need to create an IoT Hub for your device to connect to. The following steps show you how to complete this task using the Azure portal:

Step 1

Sign in to the Azure portal.

Step 2

From the left menu, Select Create a resource, then click Internet of Things, and then click IoT Hub.

Step 3

Under the Basics tab, fill in the information for your new hub.

Step 4

In Resource group, create a new resource group, or select an existing one. For more information, see Using resource groups to manage your Azure resources.

Step 5

In the Name box, enter a name for your IoT hub. If the Name is valid and available, a green check mark appears in the Name box.

Select a Pricing and scale tier. This tutorial does not require a specific tier.

Step 6

In Resource group, create a new resource group, or select an existing one. For more information, see Using resource groups to manage your Azure resources.

Step 7

In Location, select the location to host your IoT hub.

Step 8

When you have chosen your IoT hub configuration options, click Create. It can take a few minutes for Azure to create your IoT hub. To check the status, you can monitor the progress on the Startboard or in the Notifications panel.

Azure IoT Data Hub_110

When the IoT hub has been created successfully, open the blade of the new IoT hub, make a note of the Hostname, and then click the Keys icon.

Azure IoT Data Hub_111

Step 9

Click theiothubowner policy, then copy and make note of the connection string in the iothubowner blade.

Azure IoT Data Hub_112

You have now created your IoT hub and you have the hostname and connection string you need to complete the rest of this tutorial.

Azure IoT Data Hub_113

Create an IoT Driver

Step 1

Open Configure OAS

Step 2

Select Drivers

Azure IoT Data Hub_118

Step 3

Select your local or remote system

Step 4

Enter the Driver Name you wish to use.

Step 5

Select Azure IoT Driver from the combo box

Step 6

Enter the Azure IoT Device ID you want to use.

Step 7

Enter the Connection String from Step 5 of the previous section into the Azure IoT Hub Connection field.

Step 8

Enter the hostname from Step 4 of the previous section into the Azure IoT Hub URL Field.

Step 9

Select the preferred Azure IoT Transport.

Step 10

At the bottom Left of the configure application select Add.

Route Live Data to your Azure IoT Hub.

Step 1

Select Configure Tags.

Step 2

From the demo tags select the Ramp Tag.

Azure IoT Data Hub_120

Step 3

Select the Target button

Azure IoT Data Hub_121

Step 4

Enable Write to target.

Step 5

Select the Azure IoT Destination type.

Step 6

Select the Driver interface you created.

Step 7

Apply the Changes and you should now be writing to your IoT Hub.

Step 8

The message is formatted as follows.

{“deviceId”:”myFirstDevice”,”TagName”:”Ramp”,”Value”:66,”DataType”:”DoubleFloat”,”Quality”:true,”TimeStamp”:”2016-04-11T14:38:53.7125255″}

.

Getting Started OPC DA

Open Automation Software Tags can be defined to connect to Classic OPC Data Access 2.xx and 3.0 Servers with the built in OPC Interface.

The following steps can be used to setup communications with Classic Data Access OPC Servers.

Step 1 – Configure OPC DA Tags

OAS

Start Configure OAS application from the program group Open Automation Software.

Select Configure-Tags.

Menu Configure Tags

NOTE: To configure remote OAS Engines enter the IP Address or node name in the Network Node field and click on Select.

Network Node

Select Add Group to add a group to place tags in.

Add Group
Add Tag Group

NOTE: You can add organizational Groups as many levels deep as you prefer and add tags to groups.  To do this first add a Group to the root level, then right click on the Group in the right window to add additional Groups or Tags.

Tag Group Options

Select Add Tag to add a tag to the group selected.

Add Tag
Add Tag to Group

Change the Data Source Tag property to OPC.

OPC Data Source

Use the Browse button to the right of the OPC Item to browse OPC Servers for the desired OPC Item.

Getting Started OPC Item

Select Local, the desired OPC Server, branch within the OPC Server, and OPC Item and click OK.
Getting Started OPC Browse

Note: If you wish to browse remote OPC servers via IP address see Networking OPC Data.

Specify the desired OPC Update Rate for the Tag.

Getting Started OPC Update Rate

To disable communications to the device you can use Enable by Tag to control when communications is active. Leave this property disabled to establish communications at all times.

Enable by Tag

The Device Read property can be used to disable continuous polling and request data on event from the transition from false to true of a value of a Boolean tag. Leave this property disabled to establish communications at all times.

Device Read

Select Apply Changes in the lower right to activate the communications for the OPC Item.

Apply Changes

The value from OPC Server for the OPC Item selected will appear in the current Value field.

Good Quality

NOTE: If the data quality is Bad Quality view the article Troubleshooting OPC Communications.

Step 2 – Define Multiple OPC Tags

To define multiple tags use one of the following optional methods.

  • Use One Click OPC to automatically create tags from all OPC Items from a selected OPC Server or branch within an OPC Server. Then selectively delete the groups and tags that are not required.
  • Use CSV Export and CSV Import on the toolbar together with Microsoft Excel to add or modify tags.
  • Programmatically define Tags using the free to use OASConfig component with the SetTagProperties method.
  • Programmatically define Tags with the OAS REST API.

Optionally define a secondary failover OPC Server if the primary OPC Servers fails under Configure-Options-OPC.

Step 3 – Save OPC DA Tags

Select the Save button on the toolbar at the top.

Load and Save

Enter a file name to be saved in C:\ProgramData\OpenAutomationSoftware\ConfigFiles directory on Windows or ConfigFiles subdirectory on Linux.

When prompted to set the file as the default configuration to load on startup select Yes.

Set Default Tag File

The tags defined are now ready for use in all OAS features like Data Logging, Data Route, and Open UIEngine.

Create Universal Driver Interface

The first step in creating a custom communication driver for OAS is to create the Driver Assembly. This .NET Assembly will be referenced by a .NET Hosting Application, and depending on the target deployment platforms, can be developed to be cross-platform compatible for use on Windows, Mac, Linux, Android, and iOS operating systems.

Assembly Installation

  • All OAS .NET Assemblies are distributed with the OAS Platform and located within the installation directory
  • Optionally, you can install the assembly package from NuGet within Visual Studio
    Direct link to the OASIOT.OASData package:  https://www.nuget.org/packages/OASIOT.OASDriverInterface

Choosing a Framework

If you would like the flexibility of being able to run your driver on multiple operating systems, your driver should target the .NET Standard 2.0+ framework. Yet, we understand that in some cases you must rely on other 3rd party libraries that are not compatible, or you cannot upgrade your infrastructure to the NET Framework 4.6.1+. In these cases, your driver must target the .NET 4.0 Framework, and will be limited to being run on a Windows platform.

In the OAS Platform distribution, we have provided Examples for any of these scenarios. In the installation directory, locate the Universal Drivers folder, to find all Example code for drivers and Hosting Apps, in both C# and VB.NET.

For cross-platform support, choose the examples under the Standard directory, and for .NET 4.0 or 4.5 only support, select examples under the Framework4 directory.

The Driver Architecture

Building a functional UDI Driver includes the development of your own Driver assembly following our example code, then running this within a Hosting Application. In this tutorial, we will be building a .NET Framework 4.0 Driver hosted within a Windows Service to be deployed on a Windows Server or PC.

Prerequisites

You will need to be running Open Automation Software Version 11.00.0017 or greater to support Universal Drivers.  You can download the latest version at /download/

You will also need Microsoft Visual Studio to work with any of these examples and to compile your custom driver. If you do not currently have Visual Studio installed, you can try out Visual Studio Community which is a fully functional, free version of the IDE.


Step 1

Copy the C# or VB Driver Code from the OAS installation path, typically C:\Program Files\Open Automation Software\OAS\Universal Drivers\Framework4\Driver\ to a location of your choosing, then open the project in Visual Studio.

NOTE: Under the project references, if you are getting a warning or if the OASDriverInterface (or OASDriverInterfaceFramework4 for a .NET Framwework 4.0 Driver project) assembly is missing, remove the reference and add it back in, locating it in the base OAS installation directory.


Step 2

Under the Project Properties set the Assembly name and Root namespace name to something unique for each driver you create. The default is ExampleDriver, so we’ll leave this as is but you should change this to uniquely identify your driver for each data source.

The Assembly Name will be the resulting DLL that is compiled. In this case, the assembly will compile to a file named ExampleDriver.dll.

The Default Namespace will only affect new classes and files added to your driver. You may also search and replace all references of ExampleDriver with your own custom driver name so that it displays properly in all other project references.


Step 3

Open the file DriverInterface.vb for the VB project or DriverInterface.cs for the C# project and set the m_DriverName variable to a unique name for each driver you create.

This string is the display name that will appear within the OAS Configuration application. The driver name will display in the following formats.

In the list of Drivers associated with the OAS Server, you will see UDI drivers in the left pane as:

   <Driver Name>-<MachineName>

The <Machine Name> will be defined in the host application configuration file (detailed in the Hosting App tutorial), so this can be changed upon deployment and will help with identifying instances of the same drivers hosted on different or remote machines.

In the dropdown lists of drivers, including the dropdowns used to select the data source of a Tag, the UDI Driver will be displayed as:

   UDI <Driver Name>

The Driver Name is set within the source code by setting the m_DriverName string.

VB

Private m_DriverName As String = "Example Driver"

C#

private string m_DriverName = "Example Driver";

The example below shows how the driver would be listed in the OAS Configuration app if the m_DriverName string was set to “Example Driver”.

The driver name also becomes an available selection for Data Source in the Tag parameters for Value and Alarm Limits.


Step 4

Define the Driver Interface properties you would like make adjustable in the function GetDefaultDriverConfig with a list of ClassProperty objects.

The collection of DriverProps returned in the GetDefaultDriverConfig defines the list of configurable properties for the driver which will be exposed in the OAS Configuration application.

Below is an example that adds several properties, including one called “Driver Type” that selectively triggers other property fields’ visibility depending on the value selected.

Each Driver property is an instance of a ClassProperty that can be one of many different types, including selectable options, input text fields and more.

VB

    Public Overrides Function GetDefaultDriverConfig() As List(Of ClassProperty)
        Try
            Dim DriverProps As New List(Of ClassProperty)
            DriverProps.Clear()
            ' Define the properties for the Interface.  Typically IP Address, Port Number, database connection settings, etc.
            ' Example of an enumerated value
            DriverProps.Add(New ClassProperty("DriverType", "Driver Type", "Select Driver Type
                DriverType0: Example driver type 0
                DriverType1: Example driver type 1
                DriverType2: Example driver type 2", GetType(SelectDriverType), SelectDriverType.DriverType0, ClassProperty.ePropInputType.Manual))
            ' Example of Integer values.
            ' Optionally to each property you can add a Visible property binding to the value of another control.  In this example the DriverType selected will show the appropriate integer value.
            ' The Visible binding can use | for an OR condition or & for AND condition to combine criteria for the property to be visible. 
            DriverProps.Add(New ClassProperty("DriverType0Integer", "Driver Type 0 Integer", "Example value for DriverType0", GetType(Int32), 0, ClassProperty.ePropInputType.Manual, "Visible,DriverType.DriverType0"))
            DriverProps.Add(New ClassProperty("DriverType1Integer", "Driver Type 1 Integer", "Example value for DriverType1", GetType(Int32), 1, ClassProperty.ePropInputType.Manual, "Visible,DriverType.DriverType1"))
            DriverProps.Add(New ClassProperty("DriverType1And2Integer", "Driver Type 1 and 2 Integer", "Example value for DriverType1 and DriverType2", GetType(Int32), 2, ClassProperty.ePropInputType.Manual, "Visible,DriverType.DriverType1|DriverType.DriverType2"))
            DriverProps.Add(New ClassProperty("ExampleDouble", "Example Double", "Example value for all types", GetType(Double), 1, ClassProperty.ePropInputType.Manual))
            Return DriverProps
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Configuration", 1, "GetDefaultDriverConfig Exception: " + ex.Message)
        End Try
    End Function

C#

public override List GetDefaultDriverConfig()
		{
			try
			{
				List DriverProps = new List();
				DriverProps.Clear();
				// Define the properties for the Interface.  Typically IP Address, Port Number, database connection settings, etc.
				// Example of an enumerated value
				DriverProps.Add(new ClassProperty("DriverType", "Driver Type", @"Select Driver Type
                DriverType0: Example driver type 0
                DriverType1: Example driver type 1
                DriverType2: Example driver type 2", typeof(SelectDriverType), SelectDriverType.DriverType0, ClassProperty.ePropInputType.Manual));
				// Example of Integer values.
				// Optionally to each property you can add a Visible property binding to the value of another control.  In this example the DriverType selected will show the appropriate integer value.
				// The Visible binding can use | for an OR condition or & for AND condition to combine criteria for the property to be visible. 
				DriverProps.Add(new ClassProperty("DriverType0Integer", "Driver Type 0 Integer", "Example value for DriverType0", typeof(Int32), 0, ClassProperty.ePropInputType.Manual, "Visible,DriverType.DriverType0"));
				DriverProps.Add(new ClassProperty("DriverType1Integer", "Driver Type 1 Integer", "Example value for DriverType1", typeof(Int32), 1, ClassProperty.ePropInputType.Manual, "Visible,DriverType.DriverType1"));
				DriverProps.Add(new ClassProperty("DriverType1And2Integer", "Driver Type 1 and 2 Integer", "Example value for DriverType1 and DriverType2", typeof(Int32), 2, ClassProperty.ePropInputType.Manual, "Visible,DriverType.DriverType1|DriverType.DriverType2"));
				DriverProps.Add(new ClassProperty("ExampleDouble", "Example Double", "Example value for all types", typeof(double), 1, ClassProperty.ePropInputType.Manual));
				return DriverProps;
			}
			catch (Exception ex)
			{
					m_OASDriverInterface.UpdateSystemError(true, "Configuration", 1, "GetDefaultDriverConfig Exception: " + ex.Message);
			}
			return null;
		}

ClassProperty
The ClassProperty object is used to define both Driver Interface and Tag properties that you would like to appear in the OAS Framework for system setup.
Following is a list of the variables passed into the constructor for a ClassProperty object.

    Public Sub New(ByVal newPropName As String, 
        ByVal newPropDescription As String, 
        ByVal newPropHelp As String, 
        ByVal newPropType As Type,
        ByVal newPropValue As Object, 
        ByVal newPropInputType As ePropInputType, 
        Optional ByVal newBinding As String = "", 
        Optional ByVal newUnits As String = "")
    End Sub
  • PropName
    A unique name that identifies the property. This will the be name used for CSV Import and Export and also for programmatic setup with the TagCSVImport and DriverInterfaceCSVImport methods.
  • PropDescription
    The text that will appear next to the property in the Configure OAS application.
  • PropHelp
    The text that will appear in the help window for this property in the Configure OAS application. If left blank the help icon will not appear next to the property.
  • PropType
    The data type of the property value. You can define enumerations or common data types.
  • PropValue
    This will be the default value of the property when a Driver Interface or Tag is created.
  • InputType
    This is the configuration input type to use for the property. Manual is the most commonly used input type, but there are also FileBrowse and other configuration input types that can be used to define a value in the Configure OAS application.
  • Binding
    This is an optional property that can be used to control the visibility of property in the Configure OAS application based on the values of other configuration properties. If left blank the property will always appear.
  • Units
    This is an optional property that defines the text that will appear after the property value. If left blank the text will not appear.

Example of what will appear in Configure-Drivers with this example code:


Step 5

Add code to the Connect method to be executed when the driver instance enters runtime mode.

This method is where you would do one or more of the following:

  • Execute initialization code for connecting to your data source
  • Read driver configuration properties for initialization using the GetPropValue function
  • Set up timers to trigger polling activities

In this example, we read some of the persisted properties for the driver as it was configured. Then we check a flag (m_Connected) to determine if the driver has already executed Connect, and if not, start a local Timer to begin triggering.

VB

    Public Overrides Sub Connect()
        Try
            'Add Connection Logic. m_DriverProps is a list of ClassDriverProperty in the same order of Get Driver Config
            Dim DriverType As SelectDriverType = GetPropValue(m_DriverProps, "DriverType")
            Dim DriverType0Integer As Int32 = GetPropValue(m_DriverProps, "DriverType0Integer")
            Dim DriverType1Integer As Int32 = GetPropValue(m_DriverProps, "DriverType1Integer")
            Dim DriverType2Integer As Int32 = GetPropValue(m_DriverProps, "DriverType1And2Integer")
            Dim ExampleDouble As Double = GetPropValue(m_DriverProps, "ExampleDouble")
            If Not (m_Connected) Then
                localTimer.Change(100, 100)
            End If
            m_Connected = True
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Connect", 1, "GetDefaultDriverConfig Exception: " + ex.Message)
        End Try
    End Sub

C#

public override void Connect() {
  try {
    //Add Connection Logic. m_DriverProps is a list of ClassDriverProperty in the same order of Get Driver Config
    SelectDriverType DriverType = (MyExampleOASDriver.DriverInterface.SelectDriverType)GetPropValue(m_DriverProps, "DriverType");
    int DriverType0Integer = Convert.ToInt32(GetPropValue(m_DriverProps, "DriverType0Integer"));
    int DriverType1Integer = Convert.ToInt32(GetPropValue(m_DriverProps, "DriverType1Integer"));
    int DriverType2Integer = Convert.ToInt32(GetPropValue(m_DriverProps, "DriverType1And2Integer"));
    double ExampleDouble = Convert.ToDouble(GetPropValue(m_DriverProps, "ExampleDouble"));
    if (!(m_Connected))
    {
      localTimer.Change(100, 100);
    }
    m_Connected = true;
  }
  catch (Exception ex) {
      m_OASDriverInterface.UpdateSystemError(true, "Connect", 1, "GetDefaultDriverConfig Exception: " + ex.Message);
  }
}

Step 6

Add code to the Disconnect method to be executed when the driver instance exits runtime mode. This should be used to shut down any connections and undo any initialization that was performed during the Connect method.

In this example, we set the m_Connected flag to false, stop the local Timer, and clear out any tag data using the m_Tags.Clear method.

VB

    Public Overrides Function Disconnect() As Boolean
        Try
            If Not (m_Connected) Then
                Return m_Connected
            End If

            'Add Disconnection Logic
            localTimer.Change(Timeout.Infinite, Timeout.Infinite)

            SyncLock m_Tags.SyncRoot
                m_Tags.Clear()
            End SyncLock

            m_Connected = False
            Return m_Connected
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Disconnect", 1, "GetDefaultDriverConfig Exception: " + ex.Message)
        End Try
    End Function

C#

public override bool Disconnect() {
  try {
    if (!(m_Connected)) {
      return m_Connected;
    }

    //Add Disconnection Logic
    localTimer.Change(Timeout.Infinite, Timeout.Infinite);

    lock (m_Tags.SyncRoot) {
      m_Tags.Clear();
    }

    m_Connected = false;
    return m_Connected;
  }
  catch (Exception ex)
  {
      m_OASDriverInterface.UpdateSystemError(true, "Disconnect", 1, "GetDefaultDriverConfig Exception: " + ex.Message);
  }
  return false;
}

Step 7

Define the Tag properties you would like make adjustable in the function GetDefaultTagConfig with a list of ClassProperty objects.

These are properties that will be displayed and associated with a Tag configuration when the tag source uses your Driver. These properties will be added to the Value tab in the OAS Configuration app.

These properties are also defined using the ClassProperty object and added to the m_TagProps collection.

In this example, we are adding two Tag properties for Simulation Type and Dynamic Simulation Type.

VB

    Public Overrides Function GetDefaultTagConfig() As List(Of ClassProperty)
        Try
            Dim m_TagProps As New List(Of ClassProperty)

            m_TagProps.Add(New ClassProperty("SimType", 
                "Simulation Type", 
                "The simulation type of a Parameter can be set to one of the following types.
                    Dynamic: Read only value that changes dynamically from one of the Dynamic Simuation Types
                    Static: Value is fixed and can be written to.",
                GetType(SimTypes), 
                SimTypes.Dynamic, 
                ClassProperty.ePropInputType.Manual))

            m_TagProps.Add(New ClassProperty("DynamicSimType", 
                "Dynamic Simulation Type", 
                "The dynamic simulation type of a Parameter can be set to one of the following types.
                    Ramp: Value changes from 0 to 100.
                    Random: Value changes randomly from 0 to 100
                    Sine: Value changes from -1 to 1",
                GetType(DynamicSimTypes), 
                DynamicSimTypes.Ramp, 
                ClassProperty.ePropInputType.Manual, 
                "Visible,SimType.Dynamic"))

            Return m_TagProps
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Configuration", 1, "GetDefaultTagConfig Exception: " + ex.Message)
        End Try
    End Function

C#

public override List GetDefaultTagConfig() {
  try {
    List m_TagProps = new List();

    m_TagProps.Add(new ClassProperty("SimType", 
                          "Simulation Type", 
                          @"The simulation type of a Parameter can be set to one of the following types.
                              Dynamic: Read only value that changes dynamically from one of the Dynamic Simuation Types
                              Static: Value is fixed and can be written to.", 
                          typeof(SimTypes), 
                          SimTypes.Dynamic, 
                          ClassProperty.ePropInputType.Manual));

    m_TagProps.Add(new ClassProperty("DynamicSimType",
                          "Dynamic Simulation Type", 
                          @"The dynamic simulation type of a Parameter can be set to one of the following types.
                              Ramp: Value changes from 0 to 100.
                              Random: Value changes randomly from 0 to 100
                              Sine: Value changes from -1 to 1", 
                          typeof(DynamicSimTypes), 
                          DynamicSimTypes.Ramp, 
                          ClassProperty.ePropInputType.Manual, 
                          "Visible,SimType.Dynamic"));

    return m_TagProps;
  }
  catch (Exception ex) {
        m_OASDriverInterface.UpdateSystemError(true, "Configuration", 1, "GetDefaultTagConfig Exception: " + ex.Message);
    }
    return null;
  }
}

Example of what will appear in Configure-Tags with this example code:


Step 8

Add code to the AddTags method that passes the tags to be monitored with continuous polling.

All Tag Properties defined in the driver will be available at this point and additionally TagName and PollingRate and can be inspected using the GetPropValue method.

This example demonstrates pulling the TagName, and PollingRate. To add a Tag to the continuous polling routines, the tag is then added to the m_Tags collection. We further reset the timestamp stored in the m_LastUpdateTime for the Tag which is used to track the last time the Tag value has been updated.

    
    Public Overrides Sub AddTags(ByVal Tags() As List(Of ClassProperty))
        Try
            'Add Logic. Props is a list of ClassProperty in the same order of Get Tag Config
            Dim Props As List(Of ClassProperty)
            SyncLock m_Tags.SyncRoot
                For Each Props In Tags
                    ' Use the TagName as a unique identifier for the Tag Name and Parameter being interfaced with.
                    Dim TagID As String = GetPropValue(Props, "TagName")
                    ' Use the polling rate to set the communication rate to your device or software application.
                    ' If you interface uses async callbacks with a subscription rate you could create multple collections of tags based on PollingRate.
                    Dim PollingRate As Double = GetPropValue(Props, "PollingRate")

                    If m_Tags.Contains(TagID) Then
                        m_Tags(TagID) = Props
                    Else
                        m_Tags.Add(TagID, Props)
                    End If
                    If m_LastUpdateTime.Contains(TagID) Then
                        m_LastUpdateTime.Remove(TagID)
                    End If
                Next
            End SyncLock
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Communications", 1, "AddTags Exception: " + ex.Message)
        End Try
    End Sub

C#

public override void AddTags(List[] Tags) {
  try
  {
    //Add Logic. Props is a list of ClassProperty in the same order of Get Tag Config
    lock (m_Tags.SyncRoot)
    {
      foreach (List Props in Tags)
      {
        // Use the TagName as a unique identifier for the Tag Name and Parameter being interfaced with.
        string TagID = Convert.ToString(GetPropValue(Props, "TagName"));
        // Use the polling rate to set the communication rate to your device or software application.
        // If you interface uses async callbacks with a subscription rate you could create multiple collections of tags based on PollingRate.
        double PollingRate = Convert.ToDouble(GetPropValue(Props, "PollingRate"));

        if (m_Tags.Contains(TagID))
        {
          m_Tags[TagID] = Props;
        }
        else
        {
          m_Tags.Add(TagID, Props);
        }
        if (m_LastUpdateTime.Contains(TagID))
        {
          m_LastUpdateTime.Remove(TagID);
        }
      }
    }
  }
  catch (Exception ex)
  {
      m_OASDriverInterface.UpdateSystemError(true, "Communications", 1, "AddTags Exception: " + ex.Message);
  }
}

Step 9

Add code to the RemoveTags method that passes the tags to be removed from continuous polling. This should reverse the code added to the AddTags method by removing tags from continuous polling.

This example demonstrates the removal of all Tags from the m_Tags collection and clears out the associated timestamps from the m_LastUpdateTime collection.

    Public Overrides Sub RemoveTags(ByVal Tags() As String)
        Try
            Dim TagID As String
            SyncLock m_Tags.SyncRoot
                For Each TagID In Tags
                    If m_Tags.Contains(TagID) Then
                        m_Tags.Remove(TagID)
                    End If
                    If m_LastUpdateTime.Contains(TagID) Then
                        m_LastUpdateTime.Remove(TagID)
                    End If
                Next
            End SyncLock
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Communications", 2, "RemoveTags Exception: " + ex.Message)
        End Try
    End Sub

C#

public override void RemoveTags(string[] Tags) {
  try {
    lock (m_Tags.SyncRoot) {
      foreach (string TagID in Tags) {
        if (m_Tags.Contains(TagID)) {
          m_Tags.Remove(TagID);
        }
        if (m_LastUpdateTime.Contains(TagID)) {
          m_LastUpdateTime.Remove(TagID);
        }
      }
    }
  }
  catch (Exception ex) {
      m_OASDriverInterface.UpdateSystemError(true, "Communications", 2, "RemoveTags Exception: " + ex.Message);
  }
}

Step 10

Optionally, you can have your custom Driver create and configure Tags on the server in which it is installed by implementing the GetTagsToAdd method. This example demonstrates adding tags to the configuration so that data will begin flowing as soon as the Driver is activated.

As you can see, we are also using the ClassProperty to define all of the Tags’ configuration properties and even ensure that they connect using the custom Driver as a source. However, you can configure any other Tags that you choose, truly making the deployment of a Driver a hands-off operation.

VB

Public Function GetTagsToAdd() As List(Of List(Of ClassProperty))
    Try
        Dim m_Tags As List(Of List(Of ClassProperty)) = New List(Of List(Of ClassProperty))()
        Dim DynamicSimTypesToAdd As String() = {"Ramp", "Random", "Sine"}
        Dim DynamicSimTypeToAdd As DynamicSimTypes = 0

        For Each DynamicSimTypeString As String In DynamicSimTypesToAdd
            Dim m_TagProps As List(Of ClassProperty) = New List(Of ClassProperty)()
            m_TagProps.Add(New ClassProperty("Tag", "", "", GetType(String), m_DriverName & "." + m_MachineName & "." & DynamicSimTypeString, ClassProperty.ePropInputType.Manual))
            m_TagProps.Add(New ClassProperty("Value - Data Type", "", "", GetType(String), "Double", ClassProperty.ePropInputType.Manual))
            m_TagProps.Add(New ClassProperty("Value - Source", "", "", GetType(String), "UDI " & m_DriverName, ClassProperty.ePropInputType.Manual))
            m_TagProps.Add(New ClassProperty("Value - Driver Interface", "", "", GetType(String), m_DriverName & "-" + m_MachineName, ClassProperty.ePropInputType.Manual))
            m_TagProps.Add(New ClassProperty("Value - UDI " & m_DriverName & " SimType", "", "", GetType(SimTypes), SimTypes.Dynamic, ClassProperty.ePropInputType.Manual))

            Select Case DynamicSimTypeString
                Case "Ramp"
                    DynamicSimTypeToAdd = DynamicSimTypes.Ramp
                Case "Random"
                    DynamicSimTypeToAdd = DynamicSimTypes.Random
                Case "Sine"
                    DynamicSimTypeToAdd = DynamicSimTypes.Sine
            End Select

            m_TagProps.Add(New ClassProperty("Value - UDI " & m_DriverName & " DynamicSimType", "", "", GetType(DynamicSimTypes), DynamicSimTypeToAdd, ClassProperty.ePropInputType.Manual))
            m_Tags.Add(m_TagProps)
        Next

        Return m_Tags
    Catch ex As Exception
        m_OASDriverInterface.UpdateSystemError(True, "Configuration", 4, "GetTagsToAdd Exception: " & ex.Message)
        Return Nothing
    End Try
End Function

C#

public List<List> GetTagsToAdd() {
  try {
    List<List> m_Tags = new List<List>();
    string[] DynamicSimTypesToAdd = {"Ramp", "Random", "Sine"};
    DynamicSimTypes DynamicSimTypeToAdd = 0;

    foreach (string DynamicSimTypeString in DynamicSimTypesToAdd) {
      List m_TagProps = new List();
      // The name of the tag to add. Use periods to create tags within groups, each group seperated by a period.
      m_TagProps.Add(new ClassProperty("Tag", "", "", typeof(string), m_DriverName + "." + m_MachineName + "." + DynamicSimTypeString, ClassProperty.ePropInputType.Manual));

      // Include any property values of the Tag to set.  If the Tag already exists these values will be update the existing Tag.
      // Use the same property names as that are set in a Tag CSV Export

      // The Data Type of Tag value
      m_TagProps.Add(new ClassProperty("Value - Data Type", "", "", typeof(string), "Double", ClassProperty.ePropInputType.Manual));
      // The Data Source of Tag value
      m_TagProps.Add(new ClassProperty("Value - Source", "", "", typeof(string), "UDI " + m_DriverName, ClassProperty.ePropInputType.Manual));
      // The Driver Interface of Tag value
      m_TagProps.Add(new ClassProperty("Value - Driver Interface", "", "", typeof(string), m_DriverName + "-" + m_MachineName, ClassProperty.ePropInputType.Manual));

      m_TagProps.Add(new ClassProperty("Value - UDI " + m_DriverName + " SimType", "", "", typeof(SimTypes), SimTypes.Dynamic, ClassProperty.ePropInputType.Manual));

      switch (DynamicSimTypeString) {
        case "Ramp":
          DynamicSimTypeToAdd = DynamicSimTypes.Ramp;
          break;
        case "Random":
          DynamicSimTypeToAdd = DynamicSimTypes.Random;
          break;
        case "Sine":
          DynamicSimTypeToAdd = DynamicSimTypes.Sine;
          break;
      }
      m_TagProps.Add(new ClassProperty("Value - UDI " + m_DriverName + " DynamicSimType", "", "", typeof(DynamicSimTypes), DynamicSimTypeToAdd, ClassProperty.ePropInputType.Manual));
      m_Tags.Add(m_TagProps);
    }
    return m_Tags;
  }
  catch (Exception ex) {
    m_OASDriverInterface.UpdateSystemError(true, "Configuration", 4, "GetTagsToAdd Exception: " + ex.Message);
    return null;
  }
}

Step 11

Optionally add code to the SyncRead method that passes a tags list, each containing properties. This is for one time read of values based on a trigger in OAS for a Device Read. If you do not need support for the Device Read feature this method can be left blank. For continuous monitoring refer to Step 13 with the AsyncReadCallback method.

If you are connecting to a data source or application, this is where you may pull values from an API or memory cache just once on each call made to the method for the Tags requested.

    
    ' This call is performed when a Device Read is executed in OAS.
    Public Overrides Function SyncRead(ByVal Tags() As List(Of ClassProperty)) As ClassTagValue()
        Try
            Dim currentTime As Date = Now
            Dim localSeconds As Double = currentTime.Second + (currentTime.Millisecond / 1000)
            Dim TagItems As List(Of ClassProperty)

            Dim localArrayList As New ArrayList

            SyncLock m_StaticTagValues.SyncRoot
                For Each TagItems In Tags
                    Dim TagID As String = GetPropValue(TagItems, "TagName")
                    Dim SimType As SimTypes = GetPropValue(TagItems, "SimType")
                    Dim Value As Object
                    Select Case SimType
                        Case SimTypes.Dynamic
                            Dim DynamicType As DynamicSimTypes = GetPropValue(TagItems, "DynamicSimType")
                            Select Case DynamicType
                                Case DynamicSimTypes.Ramp
                                    Value = localSeconds * 100 / 60
                                Case DynamicSimTypes.Random
                                    Value = Rnd() * 100
                                Case DynamicSimTypes.Sine
                                    Value = Math.Sin(Math.PI * (localSeconds * 360 / 60) / 180.0)
                            End Select
                        Case SimTypes.Static
                            If m_StaticTagValues.Contains(TagID) Then
                                Value = m_StaticTagValues(TagID)
                            Else
                                Value = 0
                            End If
                    End Select
                    Dim Quality As Boolean = False
                    If Not (Value Is Nothing) Then
                        Quality = True
                    End If
                    localArrayList.Add(New ClassTagValue(TagID, Value, currentTime, Quality))
                Next
            End SyncLock

            Return CType(localArrayList.ToArray(GetType(ClassTagValue)), ClassTagValue())
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Communications", 3, "SyncRead Exception: " + ex.Message)
        End Try

    End Function

C#

// This call is performed when a Device Read is executed in OAS.
public override ClassTagValue[] SyncRead(List[] Tags) {
  try {
    DateTime currentTime = DateTime.Now;
    double localSeconds = currentTime.Second + (currentTime.Millisecond / 1000.0);
    ArrayList localArrayList = new ArrayList();

    lock (m_StaticTagValues.SyncRoot) {
      foreach (List TagItems in Tags) {
        string TagID = Convert.ToString(GetPropValue(TagItems, "TagName"));
        SimTypes SimType = (MyExampleOASDriver.DriverInterface.SimTypes)GetPropValue(TagItems, "SimType");
        object Value = null;
        switch (SimType) {
          case SimTypes.Dynamic:
            DynamicSimTypes DynamicType = (MyExampleOASDriver.DriverInterface.DynamicSimTypes)GetPropValue(TagItems, "DynamicSimType");
            switch (DynamicType) {
              case DynamicSimTypes.Ramp:
                Value = localSeconds * 100 / 60;
                break;
              case DynamicSimTypes.Random:
                Value = Microsoft.VisualBasic.VBMath.Rnd() * 100;
                break;
              case DynamicSimTypes.Sine:
                Value = Math.Sin(Math.PI * (localSeconds * 360 / 60) / 180.0);
                break;
            }
            break;
          case SimTypes.Static:
            if (m_StaticTagValues.Contains(TagID)) {
              Value = m_StaticTagValues[TagID];
            }
            else {
              Value = 0;
            }
            break;
        }
        bool Quality = false;
        if (Value != null) {
          Quality = true;
        }
        localArrayList.Add(new ClassTagValue(TagID, Value, currentTime, Quality));
      }
    }
    return (ClassTagValue[])localArrayList.ToArray(typeof(ClassTagValue));
  }
  catch (Exception ex) {
      m_OASDriverInterface.UpdateSystemError(true, "Communications", 3, "SyncRead Exception: " + ex.Message);
  }
  return null;
}

Step 12

Add code to the WriteValues method that passes a list Tag name, values to be written, and a list of property values for each Tag. This section of code is where you would receive tag names, values, and properties defined in the OAS Platform for each tag for tags that are to be written.

In this example simulation the values are added to the m_StaticTagValues collection for persistence in this simulation, most likely they would be sent back to the data source of you are implementing a writeable interface to your source.

    Public Overrides Sub WriteValues(ByVal TagIDs() As String, ByVal Values() As Object, TagProperties() As List(Of ClassProperty))
        Try
            'Add write Logic to actual driver
            Dim Index As Int32
            For Index = 0 To TagIDs.GetLength(0) - 1
                Dim SimType As SimTypes = GetPropValue(TagProperties(Index), "SimType")
                If SimType = SimTypes.Static Then
                    SyncLock m_StaticTagValues.SyncRoot
                        If m_StaticTagValues.Contains(TagIDs(Index)) Then
                            m_StaticTagValues(TagIDs(Index)) = Values(Index)
                        Else
                            m_StaticTagValues.Add(TagIDs(Index), Values(Index))
                        End If
                    End SyncLock
                End If
            Next
        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Communications", 4, "WriteValues Exception: " + ex.Message)
        End Try
    End Sub

C#

public override void WriteValues(string[] TagIDs, object[] Values, List[] TagProperties) {
  try {
    //Add write Logic to actual driver
    int Index = 0;
    for (Index = 0; Index < TagIDs.GetLength(0); Index++) {
      SimTypes SimType = (MyExampleOASDriver.DriverInterface.SimTypes)GetPropValue(TagProperties[Index], "SimType");
      if (SimType == SimTypes.Static) {
        lock (m_StaticTagValues.SyncRoot) {
          if (m_StaticTagValues.Contains(TagIDs[Index])) {
            m_StaticTagValues[TagIDs[Index]] = Values[Index];
          }
          else {
            m_StaticTagValues.Add(TagIDs[Index], Values[Index]);
          }
        }
      }
    }
  }
  catch (Exception ex) {
      m_OASDriverInterface.UpdateSystemError(true, "Communications", 4, "WriteValues Exception: " + ex.Message);
  }
}

Step 13

Add code for continuous polling that will obtain values for the tags and call the method AsyncReadCallback on the OAS Driver Interface object which will pass an array of tag values.

This code is triggered by the Timer that is started and stopped in the Connect and Disconnect methods. In this routine, you would perform periodic updates on your Tags and mark them as updated by recording an associated timestamp in the m_LastUpdateTime collection.

You can also see in this example that our Tags marked as “Dynamic” are provided calculated values, and “Static” tags pull values from the m_StaticTagValues collection which is set in the WriteValues routine.

    ' This is a simple example of getting the properties of a tag and using that to generate a update to the tag value
    Private Sub TimerRoutine(ByVal State As Object)
        Try
            If m_InTimerRoutine Then
                Exit Sub
            End If
            m_InTimerRoutine = True
            Dim currentTime As Date = Now
            Dim localSeconds As Double = currentTime.Second + (currentTime.Millisecond / 1000)

            Dim localArrayList As New ArrayList

            SyncLock m_Tags.SyncRoot
                SyncLock m_StaticTagValues.SyncRoot
                    Dim TagItems As List(Of ClassProperty)
                    For Each de As DictionaryEntry In m_Tags
                        Dim TagID As String = de.Key
                        TagItems = de.Value

                        ' Just simulating using the PollingRate property
                        Dim OKToPoll As Boolean = True
                        If m_LastUpdateTime.Contains(TagID) Then
                            Dim PollingRate As Double = GetPropValue(TagItems, "PollingRate")
                            Dim lastUpdateTime As Date = m_LastUpdateTime(TagID)
                            If lastUpdateTime.AddSeconds(PollingRate) > currentTime Then
                                OKToPoll = False
                            End If
                        End If

                        If OKToPoll Then
                            If m_LastUpdateTime.Contains(TagID) Then
                                m_LastUpdateTime(TagID) = currentTime
                            Else
                                m_LastUpdateTime.Add(TagID, currentTime)
                            End If
                            Dim SimType As SimTypes = GetPropValue(TagItems, "SimType")
                            Dim Value As Object
                            Select Case SimType
                                Case SimTypes.Dynamic
                                    Dim DynamicType As DynamicSimTypes = GetPropValue(TagItems, "DynamicSimType")
                                    Select Case DynamicType
                                        Case DynamicSimTypes.Ramp
                                            Value = localSeconds * 100 / 60
                                        Case DynamicSimTypes.Random
                                            Value = Rnd() * 100
                                        Case DynamicSimTypes.Sine
                                            Value = Math.Sin(Math.PI * (localSeconds * 360 / 60) / 180.0)
                                    End Select
                                Case SimTypes.Static
                                    If m_StaticTagValues.Contains(TagID) Then
                                        Value = m_StaticTagValues(TagID)
                                    Else
                                        Value = 0
                                    End If
                            End Select
                            Dim Quality As Boolean = False
                            If Not (Value Is Nothing) Then
                                Quality = True
                            End If
                            ' You can include multiple values to the same tag with different timestamps in the same callback if you like.
                            ' In this example it just updates when the timer fires and the check for the PollingRate succeeds.
                            localArrayList.Add(New ClassTagValue(TagID, Value, currentTime, Quality))

                        End If
                    Next
                End SyncLock
            End SyncLock

            If localArrayList.Count > 0 Then
                ' Send values to OAS Service
                m_OASDriverInterface.AsyncReadCallback(CType(localArrayList.ToArray(GetType(ClassTagValue)), ClassTagValue()))
            End If

            ' The following can be used in any routine to post an error during Runtime operation of OAS.
            'm_OASDriverInterface.UpdateSystemError(True, "Communications", 1, "An example of posting a system error")

        Catch ex As Exception
            m_OASDriverInterface.UpdateSystemError(True, "Communications", 5, "TimerRoutine Exception: " + ex.Message)
        End Try
        m_InTimerRoutine = False
    End Sub

C#

// This is a simple example of getting the properties of a tag and using that to generate a update to the tag value
private void TimerRoutine(object State) {
  try {
    if (m_InTimerRoutine) {
      return;
    }
    m_InTimerRoutine = true;
    DateTime currentTime = DateTime.Now;
    double localSeconds = currentTime.Second + (currentTime.Millisecond / 1000.0);
    ArrayList localArrayList = new ArrayList();

    lock (m_Tags.SyncRoot) {
      lock (m_StaticTagValues.SyncRoot) {
        List TagItems = null;
        foreach (DictionaryEntry de in m_Tags) {
          string TagID = Convert.ToString(de.Key);
          TagItems = (List)de.Value;

          // Just simulating using the PollingRate property
          bool OKToPoll = true;
          if (m_LastUpdateTime.Contains(TagID)) {
            double PollingRate = Convert.ToDouble(GetPropValue(TagItems, "PollingRate"));
            DateTime lastUpdateTime = Convert.ToDateTime(m_LastUpdateTime[TagID]);
            if (lastUpdateTime.AddSeconds(PollingRate) > currentTime) {
              OKToPoll = false;
            }
          }

          if (OKToPoll) {
            if (m_LastUpdateTime.Contains(TagID)) {
              m_LastUpdateTime[TagID] = currentTime;
            }
            else {
              m_LastUpdateTime.Add(TagID, currentTime);
            }
            SimTypes SimType = (MyExampleOASDriver.DriverInterface.SimTypes)GetPropValue(TagItems, "SimType");
            object Value = null;
            switch (SimType) {
              case SimTypes.Dynamic:
                DynamicSimTypes DynamicType = (MyExampleOASDriver.DriverInterface.DynamicSimTypes)GetPropValue(TagItems, "DynamicSimType");
                switch (DynamicType) {
                  case DynamicSimTypes.Ramp:
                    Value = localSeconds * 100 / 60;
                    break;
                  case DynamicSimTypes.Random:
                    Value = Microsoft.VisualBasic.VBMath.Rnd() * 100;
                    break;
                  case DynamicSimTypes.Sine:
                    Value = Math.Sin(Math.PI * (localSeconds * 360 / 60) / 180.0);
                    break;
                }
                break;
              case SimTypes.Static:
                if (m_StaticTagValues.Contains(TagID)) {
                  Value = m_StaticTagValues[TagID];
                }
                else {
                  Value = 0;
                }
                break;
            }
            bool Quality = false;
            if (Value != null) {
              Quality = true;
            }
            // You can include mutiple values to the same tag with different timestamps in the same callback if you like.
            // In this example it just updates when the timer fires and the check for the PollingRate succeeds.
            localArrayList.Add(new ClassTagValue(TagID, Value, currentTime, Quality));
          }
        }
      }
    }
    if (localArrayList.Count > 0)
    {
      // Send values to OAS Service
      m_OASDriverInterface.AsyncReadCallback((ClassTagValue[])localArrayList.ToArray(typeof(ClassTagValue)));
    }

    // The following can be used in any routine to post an error during Runtime operation of OAS.
    //m_OASDriverInterface.UpdateSystemError(True, "Communications", 1, "An example of posting a system error")
  }
  catch (Exception ex) {
      m_OASDriverInterface.UpdateSystemError(true, "Communications", 5, "TimerRoutine Exception: " + ex.Message);
  }
  m_InTimerRoutine = false;
}

Step 14

To post a System Error to OAS call the method UpdateSystemError on the OAS Driver Interface object.
Set the first parameter to true to post the System Error as active and false to clear the System Error that has been previously posted.
Specify a Category to group the error along with a unique MessageID.

VB

m_OASDriverInterface.UpdateSystemError(True, "myCategory", 1, "Error message to display")

C#

if (UpdateSystemError != null)
     m_OASDriverInterface.UpdateSystemError(true, "myCategory", 1, "Message to display when error is cleared");

NEXT STEPS

Creating a Hosting App

Once you have compiled the custom Driver assembly, your next step will be to Create a Hosting Application that will load the assembly and run it. This application can be any .NET app, but for most deployments, you will be creating a .NET Windows Service or a Console Application. Services are perfect for deployment on a Windows platform, and Console Applications, when written using .NET Core, can be hosted on non-Windows operating systems like Linux and Mac. Additionally, you can create an Android or iOS app using the Xamarin development tools within Visual Studio.

Getting started: OAS REST API and native iOS

Step 1

To read and write data between a native iOS app and OAS, you can use the OAS REST API. As a first step, register the HTTP listener with the OAS Service Control Manager under the section labeled “HTML HMI Registration”.

Define the exclusive Node Name and Port Number  that is to be supported. The Node Name should be the registered domain name, IP address, or network node name that all clients will connect to from their browsers. If you are unsure of which node name to use, localhost will work in most cases.

NOTE: Before clicking “Register”, be sure to stop all services. If services are running, the Service Control app will prompt you to stop them.

Step 2

Start up all services, and be sure to include the Framework 4.5 Service as this houses the listener specific to the REST API.

Step 3

Test your API Service from your iOS device.

You will now ensure that your iPhone or iPad can communicate with OAS via the REST API. In this example, the OAS host is located at 192.168.0.101 on the local network, but you will have to replace this with the OAS host node on your network. The base URL you will use to connect to the REST API is http://{{node}}:{{port}}/OASREST/v2. Each operation performed against the REST API will use the specific operation name prepended with the base {{url}}. The first operation to complete is authentication. All sessions with the REST API must be authenticated. You can adjust the {{url}} in the following snippet and test authentication. In Xcode, create a new single-view iOS application, and choose Swift as the language. Add the following code to MainViewController.swift:

Authenticate Session (Swift):

import requests
import json

### base url is http://{{node}}:{{port}}/OASREST/v2/
url = 'http://192.168.0.101:58725/OASREST/v2'  # {{url}}
op = '/authenticate'                           # {{operation}}
data = '''{"username":"", "password":""}'''
headers = {'Content-Type':'application/json'}

### Authentication is completed via a POST request
response = requests.request("POST", url+op, data=data,
                             headers=headers)

print(response.text)

### Upon success, store your clientid and token for the remainder of the session
json_data = json.loads(response.text)
clientid = json_data["data"]["clientid"]
token = json_data["data"]["token"]

If this code is executed without error, the response will include a clientid and token. These will be included in the header for all other operations.

{
  "status":"OK",
  "data":{"clientid":"531041fa-e601-49d7-a02a-cf13f12eae28",
          "token":"919a303c-4537-4658-b4bf-242fc44e6493"},
  "messages":["Default credential provided"]
}

Here are some Python examples of basic HTTP requests you might wish to make via the REST API after having completed authentication. For additional information, see our REST API documentation.

Create a Tag (Python 3):

### code continues from above
### Create a Tag
headers.update({"clientid":clientid})
headers.update({"token":token})
operation = '/tags'
data = '''
{
   "path":"SomeDummyTag",
   "parameters": {
        "Value": {
            "DataType":"Double",
            "ParameterSource":"Value",
            "Value":"262.262"
        }
    }
}   
'''
response = requests.request('POST', url+operation, data=data, headers=headers)
print(response.text)

Successful tag creation results in the following response from the OAS server:

{"status":"OK","messages":["CREATE COMPLETE"]}

Read a Tag (Python 3):

Reading a tag is accomplished with a GET request, using a simple query including the path to the Tag.

### Read a Tag
### operation = '/tags'
querystring= {'path':'SomeDummyTag'}
response = requests.request('GET', url+operation, params=querystring, headers=headers)
print(response.text)

A possible response from the OAS server:

[{"path":"SomeDummyTag","value":"262.262","quality":true,"type":"Float","timestamp":1521742596474}]

Update a Tag (Python 3):

Updating a tag is accomplished with a PUT request, using a payload including the path to the Tag, and the parameter(s) to be updated.

### Update a tag
### operation = '/tags'
data = '''
{
    "path":"SomeDummyTag",
    "parameters": {
        "Value": {
            "Value":"54.59"
        }
    }
}
'''
response = requests.request('PUT', url+operation, data=data, headers=headers)
print(response.text)

Update confirmation from the OAS server:

{"status":"OK","messages":["UPDATE COMPLETE"]}

Step 4

Write tag data from Raspberry Pi to OAS.
Now that you’ve established connectivity with the REST API, you can complete a Python script to write data from your device. Here is an example script which detects when a button is pushed, and forwards that data into OAS as a discrete tag value. An LED on the breadboard (see figure) indicates the current value, and the OAS server is polled at a frequency of 1 Hz to reflect the current state.

import RPi.GPIO as GPIO
import time
import requests
import json

### Define a function to toggle the state of PiBit
### (You will likely prefer to inject all dependencies in production code)
def toggleBit(state):
    data = '''{"path":"PiBit",
               "parameters":{
                   "Value":{
                       "Value":"'''+str(not state)+'''"
    }}}'''
    requests.request('PUT', url+operation, data=data, headers=headers)
    return not state

### read state of Boolean Tag
def readBooleanTag(tagName):
    querystring = {'path':tagName}
    response = requests.request('GET', url+operation, params=querystring, headers=headers)
    json_response = json.loads(response.text)
    test = json_response[0]["value"]
    return test == "True"

### Write a callback function to execute on event detection
def callback(input_pin):
    print("Input on pin",input_pin)
    current_state = GPIO.input(40)
    toggleBit(current_state)
    GPIO.output(40, not current_state)

### Authenticate session
url = 'http://192.168.0.101:58725/OASREST/v2'
operation = '/authenticate'
data = '''
{
    "username":"",
    "password":""
}
'''
headers = {"Content-Type":"application/json"}
response = requests.request('POST', url+operation, data=data, headers=headers)

### Update headers with clientid and token
json_data = json.loads(response.text)
clientid = json_data["data"]["clientid"]
token = json_data["data"]["token"]
headers.update({"clientid":clientid})
headers.update({"token":token})


### Create a tag to store a boolean
operation = '/tags'
data = '''
{
    "path":"PiBit",
    "parameters": {
         "Value": {
             "DataType":"Discrete",
             "ParameterSource":"Value",
             "Value":"False"
         }
    }
}
'''
response = requests.request('POST', url+operation, data=data, headers=headers)

### Setup Raspberry Pi IO board
GPIO.setmode(GPIO.BOARD)
GPIO.setup(36, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(40, GPIO.OUT)
GPIO.output(40, False)

### Detect when input voltage drops, and poll server for current state
GPIO.add_event_detect(36, GPIO.FALLING, callback=cb, bouncetime=100)
try:
    while True:
        time.sleep(1)
        print(readBooleanTag("PiBit"))
except KeyboardInterrupt:
    print("\nExiting\n")
    GPIO.cleanup()

For further help with the REST API, navigate to https://restapi.openautomationsoftware.com to open the REST API online documentation.

Writing data from Raspberry Pi using OAS REST API

Step 1

To write data into OAS from a Raspberry Pi, you will use the OAS REST API. As a first step, register the HTTP listener with the OAS Service Control Manager under the section labeled “HTML HMI Registration”.

Define the exclusive Node Name and Port Number  that is to be supported. The Node Name should be the registered domain name, IP address, or network node name that all clients will connect to from their browsers. If you are unsure of which node name to use, localhost will work in most cases.

NOTE: Before clicking “Register”, be sure to stop all services. If services are running, the Service Control app will prompt you to stop them.

Step 2

Start up all services, and be sure to include the Framework 4.5 Service as this houses the listener specific to the REST API.

Step 3

Test your API Service from your Raspberry Pi.

You will now ensure that the Raspberry Pi can communicate with OAS via the REST API. In this example, the OAS host is located at 192.168.0.101 on the local network, but you will have to replace this with the OAS host node on your network. The base URL you will use to connect to the REST API is http://{{node}}:{{port}}/OASREST/v2. Each operation performed against the REST API will use the specific operation name prepended with the base {{url}}. The first operation to complete is authentication. All sessions with the REST API must be authenticated. You can adjust the {{url}} in the following snippet and test authentication.

Authenticate Session (Python 3):

import requests
import json

### base url is http://{{node}}:{{port}}/OASREST/v2/
url = 'http://192.168.0.101:58725/OASREST/v2'  # {{url}}
op = '/authenticate'                           # {{operation}}
data = '''{"username":"", "password":""}'''
headers = {'Content-Type':'application/json'}

### Authentication is completed via a POST request
response = requests.request("POST", url+op, data=data,
                             headers=headers)

print(response.text)

### Upon success, store your clientid and token for the remainder of the session
json_data = json.loads(response.text)
clientid = json_data["data"]["clientid"]
token = json_data["data"]["token"]

If this code is executed without error, the response will include a clientid and token. These will be included in the header for all other operations.

{
  "status":"OK",
  "data":{"clientid":"531041fa-e601-49d7-a02a-cf13f12eae28",
          "token":"919a303c-4537-4658-b4bf-242fc44e6493"},
  "messages":["Default credential provided"]
}

Here are some Python examples of basic HTTP requests you might wish to make via the REST API after having completed authentication. For additional information, see our REST API documentation.

Create a Tag (Python 3):

### code continues from above
### Create a Tag
headers.update({"clientid":clientid})
headers.update({"token":token})
operation = '/tags'
data = '''
{
   "path":"SomeDummyTag",
   "parameters": {
        "Value": {
            "DataType":"Double",
            "ParameterSource":"Value",
            "Value":"262.262"
        }
    }
}   
'''
response = requests.request('POST', url+operation, data=data, headers=headers)
print(response.text)

Successful tag creation results in the following response from the OAS server:

{"status":"OK","messages":["CREATE COMPLETE"]}

Read a Tag (Python 3):

Reading a tag is accomplished with a GET request, using a simple query including the path to the Tag.

### Read a Tag
### operation = '/tags'
querystring= {'path':'SomeDummyTag'}
response = requests.request('GET', url+operation, params=querystring, headers=headers)
print(response.text)

A possible response from the OAS server:

[{"path":"SomeDummyTag","value":"262.262","quality":true,"type":"Float","timestamp":1521742596474}]

Update a Tag (Python 3):

Updating a tag is accomplished with a PUT request, using a payload including the path to the Tag, and the parameter(s) to be updated.

### Update a tag
### operation = '/tags'
data = '''
{
    "path":"SomeDummyTag",
    "parameters": {
        "Value": {
            "Value":"54.59"
        }
    }
}
'''
response = requests.request('PUT', url+operation, data=data, headers=headers)
print(response.text)

Update confirmation from the OAS server:

{"status":"OK","messages":["UPDATE COMPLETE"]}

Step 4

Write tag data from Raspberry Pi to OAS.
Now that you’ve established connectivity with the REST API, you can complete a Python script to write data from your device. Here is an example script which detects when a button is pushed, and forwards that data into OAS as a discrete tag value. An LED on the breadboard indicates the current value, and the OAS server is polled at a frequency of 1 Hz to reflect the current state.

import RPi.GPIO as GPIO
import time
import requests
import json

### Define a function to toggle the state of PiBit
### (You will likely prefer to inject all dependencies in production code)
def toggleBit(state):
    data = '''{"path":"PiBit",
               "parameters":{
                   "Value":{
                       "Value":"'''+str(not state)+'''"
    }}}'''
    requests.request('PUT', url+operation, data=data, headers=headers)
    return not state

### read state of Boolean Tag
def readBooleanTag(tagName):
    querystring = {'path':tagName}
    response = requests.request('GET', url+operation, params=querystring, headers=headers)
    json_response = json.loads(response.text)
    test = json_response[0]["value"]
    return test == "True"

### Write a callback function to execute on event detection
def callback(input_pin):
    print("Input on pin",input_pin)
    current_state = GPIO.input(40)
    toggleBit(current_state)
    GPIO.output(40, not current_state)

### Authenticate session
url = 'http://192.168.0.101:58725/OASREST/v2'
operation = '/authenticate'
data = '''
{
    "username":"",
    "password":""
}
'''
headers = {"Content-Type":"application/json"}
response = requests.request('POST', url+operation, data=data, headers=headers)

### Update headers with clientid and token
json_data = json.loads(response.text)
clientid = json_data["data"]["clientid"]
token = json_data["data"]["token"]
headers.update({"clientid":clientid})
headers.update({"token":token})


### Create a tag to store a boolean
operation = '/tags'
data = '''
{
    "path":"PiBit",
    "parameters": {
         "Value": {
             "DataType":"Discrete",
             "ParameterSource":"Value",
             "Value":"False"
         }
    }
}
'''
response = requests.request('POST', url+operation, data=data, headers=headers)

### Setup Raspberry Pi IO board
GPIO.setmode(GPIO.BOARD)
GPIO.setup(36, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(40, GPIO.OUT)
GPIO.output(40, False)

### Detect when input voltage drops, and poll server for current state
GPIO.add_event_detect(36, GPIO.FALLING, callback=callback, bouncetime=100)
try:
    while True:
        time.sleep(1)
        print(readBooleanTag("PiBit"))
except KeyboardInterrupt:
    print("\nExiting\n")
    GPIO.cleanup()

For further help with the REST API, navigate to https://restapi.openautomationsoftware.com to open the REST API online documentation.