CSWorks: web-based industrial automation

Of CSWorks and software development

Modbus support in CSWorks

clock February 27, 2012 23:51 by author Sergey Sorokin

Starting from version 2.1, CSWorks installation package includes Modbus TCP data source provider. Now you can build web-based solutions that can communicate with Modbus devices - controllers, modules, thermostats - any kind of hardware that supports Modbus. Here is a quick demonstration.

Hardware

We will need:

  • room thermostat that supports Modbus RTU (in this demo we use Tstat6 thermostat provided by Temco Controls);
  • Modbus TCP to serial adapter;
  • local network;
  • desktop computer (server);
  • notebook computer (client).


We have to use Modbus TCP to serial adapter, because CSWorks supports only TCP version of the Modbus protocol, and our thermostat provides only Modbus RTU (good old RS-485) connectivity. Make sure that:

  • both computers and Modbus adapter are connected to your local network;
  • thermostat is connected to the adapter via RS-485.

Modbus thermostat SCADA/HMI diagram

Software

The following software will be used:

  • CSWorks 2.1 on the server; full CSWorks version is required, CSWorks Light will not let clients from other computers access the application;
  • Microsoft VisualStudio 2010 on the server;
  • web browser on the notebook.

Build demo application

On the server computer, open VisualStudio project at C:\Program Files\CSWorks\Demo\Src\ModbusThermostatIntegrationDemo\ and build it. Please note that these connection points used in this sample are specific to the particular thermostat, your connections points will be different - see Modbus integration guide for your thermostat. Build Release|x86 (for 32-bit installations) or Release|x64 (for 64-bit installations) configuration. Build script will place the compiled application file (CSWorks.Client.ModbusThermostatIntegrationDemo.xap) and hosting HTML file (ModbusThermostatIntegrationDemo.html) to the correspondent locations under C:\Program Files\CSWorks\Demo\Web\. Your client demo application is ready.

Add LiveData source

CSWorks LiveData Service must be aware of the new data source - Modbus device. Our client application will connect to a data source called "Tstat6" (see MainPage.xaml of the client application), so we have to add a new data source with this name. Add the following piece to the LiveData Service configuration file (see C:\Program Files\CSWorks\Framework\Server\CSWorks.Server.LiveDataService.exe.config):

<modbusTcpLiveDataSource name="Tstat6" ipPort="502" ipAddress="192.168.1.71" unitId="254" sampleBufferLength="16" updateRate="500">
  <templates>
    <template name="analogOutputHoldingRegisterInt16" type="Int16" readPath="AnalogOutputHoldingRegister(($address))" canWrite="true" />
  </templates>
</modbusTcpLiveDataSource>


This demo works only with Analog Output Holding Registers, so there is only one template is defined for this data source. Please make sure that unitId attribute corresponds to the device identifier of the thermostat, otherwise CSWorks may not be able to communicate to it. When done, restart CSWorks LiveData Service.

Configure LiveData web service

LiveData web service must be aware of the "Tstat6" data source as well. Add an antry for "Tstat6" to the LiveData Web Service configuration file (C:\Program Files\CSWorks\Demo\Web\LiveDataWebService\web.config):

<liveDataTopology>
  <liveDataPartitions>
    <liveDataPartition name="partition1" primaryLiveDataServer="liveDataServer_1_primary" secondaryLiveDataServer="">
       <dataSources>
         ...
         <dataSource name="Tstat6"/>

Run the application

On the client notebook, open a browser and navigate to the CSWorks application page:

http://<server_computer_name_or_ip_address>/CSWorksDemo/ModbusThermostatIntegrationDemo.html

After a few seconds of initialization, you will see the demo reading values from the thermostat (click on the image to enlarge it):

Modbus Thermostat picture

Congratulations! Your thermostat is web-enabled now.



CSWorks 2.0 talks BACnet

clock May 31, 2011 22:02 by author Sergey Sorokin

Starting from version 2.0, CSWorks installation package includes BACnet IP data source provider. That means: now you can build web-based solutions that can communicate with BACnet devices - controllers, modules, thermostats - any kind of hardware that supports BACnet. Here is a quick demonstration.

Hardware

We will need:
- room thermostat that supports BACnet MSTP;
- BACnet IP to MSTP adapter;
- local network;
- desktop computer (server);
- notebook computer (client).

We have to use BACnet IP to MSTP adapter, because CSWorks supports only IP version of the BACnet protocol, and our thermostat provides only BACnet MSTP (good old RS-485) connectivity. Make sure that:
- both computers and BACnet adapter are connected to your local network;
- thermostat is connected to the adapter via RS-485.

BACnet thermostat in the local network diagram

Software

The following software will be used:
- CSWorks 2.0 on the server; full CSWorks version is required, CSWorks Light will not let clients from other computers access the application;
- Microsoft VisualStudio 2010 on the server;
- web browser on the notebook.

Build demo application

On the server computer, open VisualStudio project at C:\Program Files\CSWorks\Demo\Src\BacnetThermostatIntegrationDemo\ and have a look at MainPage.xaml. BACnet connection points are defined in the very beginning:

<!-- AnalogValue(0): mode (Heat, Cool, Idle, Afterhours, Unoccupied Idle, Unoccupied Heat, Unoccupied Cool) -->
<data:DoubleDataItem x:Key="mode" Id="..." DataSource="RoomThermostat01" TemplateName="analogValue" Parameters="tagIndex=0;"/>
<!-- AnalogValue(6): heating setpoint -->
<data:DoubleDataItem x:Key="heatingSP" Id="..." DataSource="RoomThermostat01" TemplateName="analogValue" Parameters="tagIndex=6;"/>
<!-- AnalogValue(7): cooling setpoint -->
<data:DoubleDataItem x:Key="coolingSP" Id="..." DataSource="RoomThermostat01" TemplateName="analogValue" Parameters="tagIndex=7;"/>
<!-- AnalogValue(20): room temperature -->
<data:DoubleDataItem x:Key="roomTemp" Id="..." DataSource="RoomThermostat01" TemplateName="analogValue" Parameters="tagIndex=20;"/>
<!-- AnalogValue(87): fan speed (off, 1, 2, 3, auto, on) -->
<data:DoubleDataItem x:Key="fanSpeed" Id="..." DataSource="RoomThermostat01" TemplateName="analogValue" Parameters="tagIndex=87;"/>


and used later in the UI definition part:

<TextBlock Text="{Binding Value, Source={StaticResource roomTemp}, ..." .../>
...
<TextBlock Text="{Binding Value, Source={StaticResource mode}, ..." .../>
...
<TextBlock Text="{Binding Value, Source={StaticResource heatingSP}, ..." .../>
...
<TextBlock Text="{Binding Value, Source={StaticResource coolingSP}, ..." .../>
...
<TextBlock Text="{Binding Value, Source={StaticResource fanSpeed}, ..." .../>



Please note that these BACnet connection points are specific to the particular thermostat used in this demo, your connections points will be different - see BACnet integration guide for your thermostat. Build Release|x86 (for 32-bit installations) or Release|x64 (for 64-bit installations) configuration. Build script will place the compiled application file (CSWorks.Client.BacnetThermostatIntegrationDemo.xap) and hosting HTML file (BacnetThermostatIntegrationDemo.html) to the correspondent locations under C:\Program Files\CSWorks\Demo\Web\. Your client demo application is ready.

Add LiveData source

CSWorks LiveData Service must be aware of the new data source - BACnet device. Our client application will connect to a data source called "RoomThermostat01" (see MainPage.xaml above), so we have to add a new data source with this name. Add the following piece to the LiveData Service configuration file (see C:\Program Files\CSWorks\Framework\Server\CSWorks.Server.LiveDataService.exe.config):

<dataSourceProviders>
  <bacnetIpLiveDataSources type="CSWorks.Server.DataSource.Bacnet.BacnetIpLiveDataSource, CSWorks.Server.BacnetIpProvider">
    ...
    <bacnetIpLiveDataSource name="RoomThermostat01" sampleBufferLength="16" deviceId="99102" ipPort="47808" updateRate="250">
      <templates>
        <template name="analogValue" type="Single" readPath="AnalogValue(($tagIndex)).PresentValue" canWrite="true" />
      </templates>
    </bacnetIpLiveDataSource>


This demo works only with AV (analog value) BACnet connection points, so there is only one template is defined for this data source. Please make sure that deviceId attribute corresponds to the device identifier of the thermostat, otherwise CSWorks will not be able to locate it in the BACnet network. When done, restart CSWorks LiveData Service.

Configure LiveData web service

LiveData web service must be aware of the "RoomThermostat01" data source as well. Add an antry for "RoomThermostat01" to the LiveData Web Service configuration file (C:\Program Files\CSWorks\Demo\Web\LiveDataWebService\web.config):

<liveDataTopology>
  <liveDataPartitions>
    <liveDataPartition name="partition1" primaryLiveDataServer="liveDataServer_1_primary" secondaryLiveDataServer="">
      <dataSources>
        ...
        <dataSource name="RoomThermostat01"/>


Run the application

On the client notebook, open a browser and navigate to the CSWorks application page:

http://<server_computer_name_or_ip_address>/CSWorksDemo/BacnetThermostatIntegrationDemo.html

After a few seconds of initialization, you will see the demo reading values from the thermostat (click on the image to enlarge it):

BACnet integration with thermostat

Congratulations! Your thermostat is web-enabled now.



CSWorks 1.7.5000.0 released

clock March 14, 2011 17:56 by author Sergey Sorokin

What's new:

  • HVAC symbol library

 

A minor release. CSWorks installation package now includes a symbol library for building automation industry. Click on the image below to see our online demo.

HVAC interactive demo: AHU

 



CSWorks 1.4.4000.0 released

clock November 18, 2010 11:57 by author Sergey Sorokin

What's new:

  • 2-way remote alarm notification: email and SMS

The following video shows CSWorks SMS and email alarm notification in action:

  • a user triggers an alarm by closing all intake valves;
  • CSWorks Alarm Notification Service detects the active alarm and sends notification email (using SMTP server) and notification SMS (using GSM modem);
  • pipeline operator gets notificatons by email and SMS;
  • the operator sends acknowledgement SMS to the alarm notification server;
  • the alarm gets acknowledged;
  • the alarm goes inactive.

 



CSWorks user authorization using third-party access management system - Lab 04

clock October 22, 2010 13:38 by author Sergey Sorokin

Introduction

In CSWorks 1.4.3900.0 we have added a possibility to restrict user access to specific data sources, alarm groups and historical data points. Inquiring users will find compelling answers awaiting them in CSWorks documentation that explains new security model in detail. In this article, I will focus on CSWorks integration with a third-party access rights management system. For the purpose of demonstration, I will be using SecureAccess 4.2 by PortSight - .NET security component for user management and application access rights management. This product meets all of the requirements to be used as an underlying access management system:

  • it can be accessed from .NET applications
  • it allows custom privileges like LiveData Read, LiveData Write etc.
  • it allows storing of resource identifiers: strings (data sources) and guids (alarm groups, historical data points)
  • it allows retrieval of all resource identifiers of a specific type (data sources, alarm groups, historical data points) available to a specific user for specific action (read, write, acknowledge)

I will walk through the process of creating a custom authorization provider for CSWorks and using it with Secure Demo application that comes as part of the CSWorks setup.

Setting up SecureAccess

Here are the steps:

  • install SecureAccess on a server computer;
  • provide it with database server access and create a new database using SecureAccess Catalog Manager, call this database "SecureAccess";
  • create the following application hierarchy: Applications->AlarmSecurity->Alarm Groups->Pipeline alarms;
  • create a role "PipelineAdmins", create a user "JohnDoe" and add him to the role;

The following screenshot of the SecureAccess administration panel shows what has been done so far:

Roles

Now make sure that Description field of the application part "PipelineAlarms" contains the id of the alarm group "All Pipes" used by CSWorks Alarm Service and defined in Alarms.xml:

...
<alarmGroup id="{4709B095-BBB6-4e48-97B8-AF15C5F19DD6}" description="All Pipes">
...

The following screenshot displays "PipelineAlarms" details:

Parts

At the level of "AlarmSecurity" application, create custom permission types ReadAlarms and AckAlarms and make sure they are available in "PipelineAlarms" application part:

Permission Types

Now the most important thing: on "PipelineAlarms" application part level, give ReadAlarms and AckAlarms permissions "PipelineAdmins" role. Permission matrix for "PipelineAlarms" will look as follows:

Permission Matrix

Now SecureAccess is ready to perform authorization for users who want to access CSWorks alarms in "All Pipes" alarm group.

Developing authorization provider

Using Microsoft Visual Studio, create a new .NET library CSWorks.Server.SecureAccessAuthorizationProvider.dll that references CSWorks.Server.WebSecurity.dll (it comes as part of the CSWorks setup) and the following SecureAccess libraries:

  • ARDataServices.dll
  • ARObjects.dll
  • ARWebSecurity.dll
  • SecureAccess.dll

Create a class SecureAccessAuthorizationProvider that implements IAuthorizationProvider interface defined in CSWorks.Server.WebSecurity:

using System;
using System.Collections.Generic;
using System.Linq;
using PortSight.SecureAccess.ARObjects;
using CSWorks.Server.Diagnostics;

namespace CSWorks.Server.WebSecurity
{
  public class SecureAccessAuthorizationProvider: IAuthorizationProvider
  {
    public void Open(string connectionString, string userName)
    {
      // Nothing to do
    }

    public void Close()
    {
      // Nothing to do
    }

    public IEnumerable GetPrivilegeGuids(string userName, Privilege privilege)
    {
      List ids = new List();
      if (privilege == Privilege.AlarmRead || privilege == Privilege.AlarmAck)
      {
        IEnumerable alarmGroupAppParts = ARHelper.GetARObjects(ARObjectTypesEnum.ARApplicationPart).OfType().Where(ap => ap.ObjectAlias.StartsWith("AlarmSecurity.AlarmGroups.", StringComparison.InvariantCultureIgnoreCase));
        string secureAccessPrivilegeName = (privilege == Privilege.AlarmRead ? "ReadAlarms" : "AckAlarms");

        // Build a list of all group ids this user is authorized to READ or ACK
        foreach (ARApplicationPart ap in alarmGroupAppParts)
        {
          if (ARHelper.IsAuthorized(userName, ap.ObjectAlias, secureAccessPrivilegeName))
          {
            // Take group id from the app part description
            ids.Add(new Guid(ap.ObjectDescription));
          }
        }
      }
      else
      {
        // Report error: privilege not supported
      }

      return ids;
    }

    public IEnumerable GetPrivilegeStrings(string userName, Privilege privilege)
    {
      throw new NotImplementedException();
    }
  }
}

The idea is very straightforward: when given a user name and alarm privilege, GetPrivilegeGuids() method should return all alarm group ids this user has correspondent access to. GetARObjects() returns all application parts under "AlarmGroups", and IsAuthorized() method filters out those alarm groups that are not accessible.

Using authorization provider with your application

Copy CSWorks.Server.SecureAccessAuthorizationProvider.dll together with all SecureAccess dependencies to the bin folder of SecurityDemo application (CSWorks\Demo\Web\bin) so secure CSWorks web services can access it. Add the following lines to the web.config of SecureAlarmWebService located at CSWorks\Demo\Web\SecureAlarmWebService (make sure you have specified proper SQL Server name or address, SQL Server user name and password, and a valid SecureAccess license key):

<configuration>
  <configSections>
    <sectionGroup name="authorizationProviderConfig">
      <section name="authorizationProviders" type="CSWorks.Server.WebSecurity.AuthorizationProviderConfigurationSection, CSWorks.Server.WebSecurity" />
    </sectionGroup>
    ...
  </configSections>
  <appSettings>
    <add key="SecureAccessConnectionString" value="data source=sqlserver;initial catalog=SecureAccess;user id=sqluser;password=sqlpassword;packet size=4096" />
    <add key="SecureAccessDefaultCulture" value="en-US" />
    <add key="SecureAccessParentFrameName" value="" />
    <add key="SecureAccessLicenseKey" value="XXXX-XXXX-XXXX-XXXX-XXXX" />
    <add key="SecureAccessVirtualPath" value="" />
    <add key="SecureAccessTopRecords" value="1000" />
    <add key="SecureAccessCacheExpiration" value="30" />
    <add key="SecureAccessApplicationAlias" value="SecureAccess" />
    <add key="SecureAccessLogonFormRedirectsTo" value="" />
  </appSettings>
  <authorizationProviderConfig>
    <authorizationProviders activeAuthorizationProvider="secureAccessAuthorizationProvider">
      ...
      <authorizationProvider name="secureAccessAuthorizationProvider" type="CSWorks.Server.WebSecurity.SecureAccessAuthorizationProvider, CSWorks.Server.SecureAccessAuthorizationProvider" connectionString="" allowCaching="true"/>
    </authorizationProviders>
  </authorizationProviderConfig>
  ...
</configuration>

SecureAlarmWebService is ready to use the provider you have just crafted. Now modify SecurityDemo.aspx.cs source file of the SecurityDemo sample application by replacing "demooperator@acme.com" with "johndoe" in the AllowAlarmPipes_Click() method as follows:

namespace CSWorks.Server.SecurityDemoWebApplication.WebApplication
{
  public partial class SecurityDemo : System.Web.UI.Page
  {
    ...

    protected void AllowAlarmPipes_Click(object sender, EventArgs e)
    {
      //System.Web.Security.FormsAuthentication.SetAuthCookie("demooperator@acme.com", true);
      System.Web.Security.FormsAuthentication.SetAuthCookie("johndoe", true);
      Response.Redirect("SecurityDemoAlarm.aspx");
    }

    ...
  }
}

From now on, when a user clicks the "Allow 'All Pipes' alarm group only (no acks)" he/she is impersonated as "John Doe" and gets read/ack access to all alarms in the group "All Pipes". All other alarm groups are not available to this user.

Running the demo

Build updated SecurityDemo application, run it and click the "Allow 'All Pipes' alarm group only (no acks)" button. Make sure that "JohnDoe" user name appears in the message on top of the page and you have read/ack access only to those alarms that belong to "All Pipes" alarm group. You can do that by running "Pipes and tanks" demo application and closing all valves that feed the mixing tank: this eventually will trigger an alarm called "Both lines are empty" in "All Pipes" group, and you will be able to see it and acknowledge it from the Security Demo.

The discussed example deals with CSWorks alarming only, but you can easily do the same for LiveData sources (use data source name for SecureAccess application part description, and implement provider's GetPrivilegeStrings() method) and historical data points (use data point id for SecureAccess application part description, and add some code to GetPrivilegeGuids() method).



CSWorks Light - no IIS required

clock September 2, 2010 20:08 by author Sergey Sorokin

Some people who are willing to try CSWorks have no possibility or desire to install IIS (Internet Information Services). Starting today, to make their lives a bit easier, we offer a special distribution of CSWorks called "CSWorks Light" that does not require IIS. A couple of highlights:

  • this distribution uses Microsoft Cassini web server - a very simple and limited-functionality, lightweight alternative to IIS;
  • this distribution misses some samples that work with IIS-hosted CSWorks;
  • this distribution should not be used in production environment, it is for demo purposes only.

Cassini-based deployment has the following limitations:

  • it can host only one ASP.NET application per port;
  • it does not support HTTPS;
  • it does not support authentication;
  • it responds only to localhost requests.

Cassini was designed as a simple tool for debugging .NET applications and it is not officially supported by Microsoft (read full story here), so please do not expect stellar performance and production-grade reliability from CSWorks Light.

The link to CSWorks Light download will be provided in the email you will receive after submitting our
download form.



Silverlight 4 - relative web service address support

clock June 25, 2010 16:59 by author Sergey Sorokin

Silverlight 4 supports the resolution of relative service addresses. Provided the Silverlight XAP package is hosted at http://somesite.com/application/ClientBin/Application.xap, the following address resolve as indicated:

"../Service1/Service.asmx" resolves to “http://somesite.com/application/Service1/Service.asmx”

For you, CSWorks users out there, it means one good thing: no more ServiceReferences.ClientConfig hassle, see Running CSWorks demo applications from remote clients post for details. Now you can simply specify relative service addres as follows:

<endpoint address="../LiveDataWebService/Service.asmx" ...>

and your CSWorks application will work from any website you deploy it to. Switching to Silverlight 4 pays off!



Resizable Alarm Summary and Trend

clock April 19, 2010 21:11 by author Sergey Sorokin

Resizable controls - what's the problem?

To save HMI screen space, application developers may want to make some controls resizable. Applying ScaleTransform is not a problem and is a good solution in many cases. See Pipes and Tanks Demo for example - it uses ScaleTransform and redraws screen content when a user changes browser window size.

Unfortunately, ScaleTransform is not an optimal solution when dealing with complex UI elements, especially those containing some amount of text - some pieces may look ugly, some may become too small to be useful. The only solution is to make complex controls able to react to the 'SizeChanged' event and recalculate internal layout at runtime. Latest version of Alarm Summary and Trend Control can do that, and this post shows how application developers can use this functionality.

We will need a container control that lets user change its size, and a piece of code that passes the changes to CSWorks controls. You can develop your own container control (there are a lot of examples available in the web) or use an existing third-party container control for that. I will use RadWindow from Telerik in this example - it serves the purpose quite well.

Resizable Alarm Demo

1. Download Rad Controls for Silverlight trial package from www.telerik.com (DLLs only).
2. Unzip downloaded archive to "C:\Program Files\CSWorks\Demo\Src\tps\Telerik". Telerik DLLs will be unpacked to "C:\Program Files\CSWorks\Demo\Src\tps\Telerik\Binaries\Silverlight\".
3. Open Alarm Demo project ("C:\Program Files\CSWorks\Demo\Src\AlarmDemo\") in Visual Studio.
4. We will use RadWindow control implemented in Telerik.Windows.Controls.Navigation.dll. Add Telerik.Windows.Controls.Navigation.dll and Telerik.Windows.Controls.dll to the reference list for Alarm Demo project.
5. Open Page.xaml file. Add 'telerik' namespace to the UserControl element:

<UserControl ...
  xmlns:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Navigation"
... >


6. Replace control content with a grid that contains RawWindow that contains CSWorks Alarm Summary, and add SizeChanged event handler for RadWindow:

<Grid>
  <telerik:RadWindow x:Name="radWindow" SizeChanged="OnSizeChanged">
    <telerik:RadWindow.Header>
      <TextBlock Text="Alarm Demo"/>
    </telerik:RadWindow.Header>
    <alarm:AlarmSummary ... />
  </telerik:RadWindow>
</Grid>


7. Open Page.xaml.cs file, implement SizeChanged event handler that adjusts AlarmSummary height and width when RadWindow size is changed: 

private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
  GeneralTransform gtGlobal = alarmSummary.TransformToVisual(radWindow);
  Point leftTopGlobal = gtGlobal.Transform(new Point(0, 0));

  alarmSummary.Height = radWindow.Height - radWindow.BorderThickness.Bottom - leftTopGlobal.Y - 4.0;
  alarmSummary.Width = radWindow.Width - leftTopGlobal.X * 2;
}


8. Update Loaded event handler so RadWindow shows itself after page is loaded:

private void Page_Loaded(object sender, RoutedEventArgs e)
{
  ...
  radWindow.Show();
}


9. Rebuild the project and run Alarm Demo from Start Menu. Resize RadWindow and watch Alarm Summary changing its layout:

Resizable Trend

 You can do the same to CSWorks Trend control.

1. Open Trend Demo project ("C:\Program Files\CSWorks\Demo\Src\TrendDemo\TrendDemo.Sample.csproj")
2. Add Telerik.Windows.Controls.Navigation.dll and Telerik.Windows.Controls.dll to the reference list.
3. Open MainPage.xaml file. Add 'telerik' namespace as shown above.
4. Change xaml so RadWindow contains TrendControl:

<Grid>
  <telerik:RadWindow x:Name="radWindow" SizeChanged="OnSizeChanged">
    <telerik:RadWindow.Header>
      <TextBlock Text="Trend Demo"/>
    </telerik:RadWindow.Header>
    <t:Trend ...>
      ...
    </t:Trend>
  </telerik:RadWindow>
</Grid>


5. Open MainPage.xanl.cs. Add OnSizeChanged handler:

private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
  GeneralTransform gtGlobal = _demoTrend.TransformToVisual(radWindow);
  Point leftTopGlobal = gtGlobal.Transform(new Point(0, 0));

  _demoTrend.Height = radWindow.Height - radWindow.BorderThickness.Bottom - leftTopGlobal.Y - 4.0;
  _demoTrend.Width = radWindow.Width - leftTopGlobal.X * 2;
}


6. Show RadWindow on startup:

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
  ...
  radWindow.Show();
}


7. Rebuild the project and run Trend Demo from Start Menu. Resize RadWindow and watch Trend control changing its layout: 




Using third-party controls

clock March 15, 2010 17:47 by author Sergey Sorokin

CSWorks client framework is all about third-party UI components. As of this writing, all major Silverlight component providers have come up with their versions of the Gauge control. In this example, I will show how you can integrate Telerik and/or ComponentOne gauge controls into CSWorks Pipes and Tanks Demo application. The result will look like this:



Here are the steps to make it work.

1. Download Telerik Silverlight controls (dlls only) and ComponentOne Studio Trial Package for Silverlight.
2. Create a folder for third-party controls - C:\Program Files\CSWorks\ThirdParty.
3. Unpack downloaded Telerik archive and install ComponentOne package.
4. Copy Telerik assemblies (Telerik.Windows.Controls.Gauge.dll, Telerik.Windows.Controls.dll, Telerik.Windows.Data.dll) and ComponentOne assemblies (C1.Silverlight.dll, C1.Silverlight.Gauge.dll) to ThirdParty folder.
5. Open C:\Program Files\CSWorks\Demo\Src\PipesAndTanksDemo\PipesAndTanksDemo.Sample.csproj in Visual Studio.
6. Add all copied third party assemblies to the project reference list.
7. Open Page.xaml and add third-party namespaces to the namespace list:

<UserControl x:Class="CSWorks.Client.PipesAndTanksDemo.Page"
  ...
  xmlns:tcontrol="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Gauge"
  xmlns:tgauge="clr-namespace:Telerik.Windows.Controls.Gauges;assembly=Telerik.Windows.Controls.Gauge"
  xmlns:c1gauge="clr-namespace:C1.Silverlight.Gauge;assembly=C1.Silverlight.Gauge"

  ...>


8. Add gauge controls to the page and bind them to LeftPumpSpeed and RightPumpSpeed data items:

<controls:TabItem Header="HMI Controls View">
    <Canvas>
        ...
        <Grid ...>
            ...
            <Border ...>
              ...
            </Border>

            <!-- Telerik Gauge for left pump -->
            <Border Grid.Row="1" Grid.Column="5" Grid.RowSpan="1" Grid.ColumnSpan="3" Background="White" BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="5,0,5,0" Margin="0,10,0,-10">
                <StackPanel Orientation="Vertical">
                    <TextBlock Text="Telerik Gauge" Foreground="Black" FontSize="8" HorizontalAlignment="Center"/>
                    <tcontrol:RadGauge x:Name="radGauge" VerticalAlignment="Bottom" >
                        <tgauge:RadialGauge  >
                            <tgauge:RadialScale Min="0" Max="200" LabelRotationMode="None" FontSize="7">
                                <tgauge:RadialScale.Label>
                                    <tgauge:LabelProperties FontSize="4.5"/>
                                </tgauge:RadialScale.Label>
                                <tgauge:RangeList>
                                    <tgauge:RadialRange Min="0" Max="100" StartWidth="0.05" EndWidth="0.05" Background="Green"/>
                                    <tgauge:RadialRange Min="100" Max="160" StartWidth="0.05" EndWidth="0.05" Background="Orange"/>
                                    <tgauge:RadialRange Min="160" Max="200" StartWidth="0.05" EndWidth="0.05" Background="Red" />
                                </tgauge:RangeList>
                                <tgauge:IndicatorList>
                                    <tgauge:Needle Value="{Binding Value, Mode=OneWay, Source={StaticResource LeftPumpSpeed}}"/>
                                </tgauge:IndicatorList>
                            </tgauge:RadialScale>
                        </tgauge:RadialGauge>
                    </tcontrol:RadGauge>
                    <TextBlock Text="rpm" Foreground="White" FontSize="7" HorizontalAlignment="Center" VerticalAlignment="Bottom"  Margin="0,-30,0,30"/>
                </StackPanel>
            </Border>

            <!-- ComponentOne Gauge for right pump -->
            <Border Grid.Row="1" Grid.Column="9" Grid.RowSpan="1" Grid.ColumnSpan="3" Background="White" BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="5,0,5,0" Margin="0,10,0,-10">
                <StackPanel Orientation="Vertical">
                    <TextBlock Text="C1 Gauge" Foreground="Black" FontSize="8" HorizontalAlignment="Center"/>
                    <c1gauge:C1RadialGauge  Minimum="0" Maximum="200" Value="{Binding Value, Mode=OneWay, Source={StaticResource RightPumpSpeed}}">
                        <c1gauge:C1GaugeLabel Interval="20" Foreground="Black" Format="#" FontSize="6" FontWeight="200" Location="0.85"/>
                        <c1gauge:C1GaugeRange From="0" To="100"  Fill="Green" Location="0.45"/>
                        <c1gauge:C1GaugeRange From="100" To="160"  Fill="Orange" Location="0.45"/>
                        <c1gauge:C1GaugeRange From="160" To="200"  Fill="Red" Location="0.45"/>
                        <c1gauge:C1GaugeMark Interval="20" />
                    </c1gauge:C1RadialGauge>
                    <TextBlock Text="rpm" Foreground="Black" FontSize="7" HorizontalAlignment="Center" VerticalAlignment="Bottom"  Margin="0,-30,0,30"/>
                </StackPanel>
            </Border>


            <TextBlock Text="All UI elements ..." .../>
          </Grid>
          ...
    </Canvas>
</controls:TabItem>


9. Build the project and start Pipes and Tanks Demo from your start menu. Enjoy professional-looking controls in action.



CSWorks demo and your OPC data source

clock March 8, 2010 19:48 by author Sergey Sorokin

After installing CSWorks, many of you are tempted to connect it to your OPC data source. Here is a quick step-by-step guide how you can do that in five minutes.

In this example, I will assume we are dealing with Matrikon OPC Simulation Server - you can download it for free from Matrikon website. After installing Matrikon components, you can start tweaking CSWorks Pipes and Tank Demo so it uses data from Matrikon OPC Server.

Add new data source to LiveData Service configuration

Add a few lines to C:\Program Files\CSWorks\Framework\Server\CSWorks.Server.LiveDataService.exe.config:

    <opcLiveDataSources type="CSWorks.Server.DataSource.Opc.OpcLiveDataSource, CSWorks.Server.OpcProvider">
      ...
      <opcLiveDataSource name="OpcMatrikon" sampleBufferLength="16" hostName="localhost" progId="Matrikon.OPC.Simulation.1" subscriptionUpdateRate="250">
        <templates>
          <template name="matrikonBoolTag" type="Boolean" readPath="Random.Boolean" canWrite="true"/>
        </templates>
      </opcLiveDataSource>

    </opcLiveDataSources>


Restart LiveData Service so your changes take effect and the service connects to the data source.

Add new data source to LiveData Server topology configuration

Add one line to C:\Program Files\CSWorks\Demo\Web\LiveDataWebService\web.config:

  <liveDataTopology>
    <liveDataPartitions>
      <liveDataPartition name="partition1" primaryLiveDataServer="liveDataServer_1_primary" secondaryLiveDataServer="">
        <dataSources>
          ...
          <dataSource name="OpcMatrikon"/>
        </dataSources>
      </liveDataPartition>
    </liveDataPartitions>
  </liveDataTopology>


Now LiveData Web Service knows where to get data for OpcMatrikon data source.

Modify demo application

Run Microsoft Visual Studio and open Pipes and Tanks Demo project at C:\Program Files\CSWorks\Demo\Src\PipesAndTanksDemo\PipesAndTanksDemo.Sample.csproj. Let's make several additions to the Page.xaml file.

Add a new data item that gets data from the boolean tag configured for Matrikon OPC Server:

    <UserControl.Resources>
        ...
        <d:BoolDataItem x:Key="TestValve" Id="type in new guid here" DataSource="OpcMatrikon" TemplateName="matrikonBoolTag" Parameters=""/>
    </UserControl.Resources>



Add a few visual elements to the upper left corner of the screen and bind them to the new data item:

        <controls:TabControl...>
            <controls:TabItem Header="HMI Controls View">
                <Canvas>
                    ...
                    <Grid ...>
                          ...
                      </Grid.ColumnDefinitions>
                        <pipes:RightValve Grid.Row="0" Grid.Column="3" IsOpen="{Binding Value, Mode=OneWay, Source={StaticResource TestValve}}" OpenColor="Black" ClosedColor="Black" IsEnabled="False" Margin="-16,0,16,0"/>
                        <PipesAndTanksDemo:Wireframe Grid.Row="0" Grid.Column="3" Status="{Binding Status, Mode=OneWay, Source={StaticResource TestValve}}" Margin="-16,0,16,0"/>
                        <pipes:RightTWay Grid.Row="0" Grid.Column="2" IsFilled="{Binding Value, Mode=OneWay, Source={StaticResource TestValve}}" FillColor="Coral" Margin="-16,0,16,0"/>
                        <PipesAndTanksDemo:Wireframe Grid.Row="0" Grid.Column="2" Status="{Binding Status, Mode=OneWay, Source={StaticResource TestValve}}" Margin="-16,0,16,0"/>

                        ...


Now we have a valve and a t-way joint that supply some coral-colored liquid to Tank 1. Also, we have added a couple of Wireframe elements that will tell us if something is wrong with Matrikon boolean tag.

Time to tell DataManager that we have a new data item. Add a command to the Page_Loaded() handler in Page.xaml.cs:

        void Page_Loaded(object sender, RoutedEventArgs e)
        {
          ...
          dataManager.DataItems.Add(Resources["TestValve"] as DataItem);


Do not forget to update the id of the datamanager in the Page.xaml, otherwise LiveData Service will keep using cached list of required data items and Matrikon tag value will never be returned:

        <d:DataManager x:Key="DataManager" Id="type in new guid here" />

We are done. Build client application and run it at http://localhost/CSWorksDemo/PipesAndTanksDemo.html (or click Start menu and choose All Programs -> CSWorks ->  Client Samples -> Pipes and Tanks Demo). The left upper corner of the screen will have some new visual elements - see screenshot.

Troubleshooting

Check names

Invalid configuration settings may prevent CSWorks LiveData Service from connecting to OPC servers. Make sure you have specified proper OPC data source attributes (opcLiveDataSource element in the config file):

  • hostName: must contain the name or the IP address of the computer running OPC server instance; if you specify host IP address, this will eliminate the need for the host name resolution, leaving less space for potential errors;
  • progId: must contain program id or guid of the OPC server; if you specify OPC server guid, this will eliminate the need to perform progid-to-guid mapping, leaving less space for potential errors.

Make sure you have specified correct read/write path(s) in template descriptions (see template elements in the data source definition) and template parameters (check your client application that supplies template parameters). If something is wrong, you will probably get an error from LiveData Service saying “Cannot add to OPC group”.

COM/DCOM security issues

In some cases, especially if you run OPC server on a separate computer, you may get security-related errors. Here is one of them: Data source <data source name>, cannot retrieve OPC server guid by program id <program id> on host <OPC server host name or IP address>.

When CSWorks LiveData service is trying to instantiate an OPC server, it calls .NET method Type.GetTypeFromProgID() method to get type information for this COM object. This method may fail for a number of reasons: improperly registered COM component, incorrect hostname or progId settings (see above), COM/DCOM security restrictions (see below) etc. Unfortunately, this piece of .NET/COM interop does not provide too much diagnostics info.

Another kind of message you may get is: Cannot create instance of OPC server <OPC server progid or guid> on machine <OPC server host name or IP address>, error Retrieving the COM class factory for remote component with CLSID <OPC server guid> from machine <OPC server host name or IP address> failed due to the following error: 80070005.

The error code 80070005 explicitly tells the reason for the failure: access to OPC server instance is denied. The following are the steps to troubleshoot COM/DCOM security issues.

Step one. Try connecting to the OPC server in question from the machine where LiveData Service is running using some simple OPC client software (Matrikon OPC Explorer is a good choice). If you fail to do that, investigate this issue using your usual DCOM troubleshooting routine. Here is a piece of advise though.

Checking “Launch and Activation” and “Access” permissions would probably be the first thing to check. In the case with Matrikon Simulation OPC Server, we did not have any issues with COM/DCOM security, mostly because Matrikon installer lets everyone access this OPC Server. You can verify it by running dcomcnfg tool on the OPC server machine and looking at Matrikon Simulation OPC Server configuration - it explicitly says that "Everyone" has "Launch and Activation" and "Access" permissions for this component, see screenshot. This cannot be considered safe practice, but makes casual user's life a bit easier.

Step two. After you have connected to the OPC server using OPC client software, try starting CSWorks LiveData Service again. You may keep getting the “access denied” error, regardless of the fact that OPC server may be already configured to be accessed by “Everyone”: “Everyone” may not include accounts based on remote machines. In our case, the problem is in the specific account CSWorks LiveData Service runs under – by default, it runs under “Network Service” Windows built-in account. Try running CSWorks LiveData Service under the account you were using on step one to run the OPC client, you probably did it using your personal account – local or domain-based. This should help in 99% of cases. After making sure that LiveData Service works, you will have to:

  • decide which account you want to use in the production environment (you probably don’t want to use your personal account for that);
  • give this account "Launch and Activation" and "Access" permissions for the OPC server;
  • configure CSWorks LiveData Service to run under this account.

64-bit implications

Please don’t expect 32-bit version of CSWorks LiveData Service to work with a 64-bit OPC server.

Desktop interaction issues

Some OPC servers may be implemented in a way they interact with the user: they may display some windows and/or add notification icons to the taskbar. Those OPC servers require special attention.

First, try to minimize OPC server desktop interaction. For example, Omron CX OPC Server software (that interacts with Windows desktop) has setting called “Start in Silent Mode” – turn it on.

Second, using dcomcnfg tool, make sure that the account CSWorks LiveData Service runs under (“Network Service”, “System”, or some non-built-in account) has ALL Launch and Access permissions for this OPC server component (do not trust the default settings, select "Customize" and check all permissions checkboxes for the given account explicitly).

Third, using dcomcnfg tool’s “Identity” tab, tell DCOM that "Launching" user should be used to run OPC server instance.

Remember: everytime you have issues connecting to OPC server, check CSWorks LiveData configuration settings spelling and COM/DCOM security as these two are by far the most common issues.