C# and ABB OPC AC 800M OPC Server – Things, what I learned.

Recently I had to work with a ABB AC 800M OPC server. OPC servers has their own challenges, so I thought I will share, a few key points, what I learned.
First of all, there are few kind of OPC servers out there, and they support different ways to communicate them. Some of are supporting UA, some are only DA, AE and HDA.
The biggest mistake was, that I thought, they will work in the same way. In fact, they are not! They have the same basics of communication, even, the same code can work very well, but it leads to big
surprises.
Because at the development time, I haven’t access the same OPC server with the same control logic on it, I needed something to test my code. I found a free OPC Server from Matrikon, which was able to set a few variables, and randomly changing items. The project required client-server architecture, so I used WCF to bridge it. At the start of the project the main goal was to read variables. But surprisingly the penultimate day, got news; that my program need to be able to give control commands either. Since that time, my service was finished and tested; I didn’t want to touch it. I made a separate service, to be responsible for commanding. Sadly, at that time I was able to test it only with Matrikon OPC server, not with the ABB, which one is the production. My test went fine, and I needed only one night to rewire my program, thanks for the design logic which I chosen at the beginning. After it, at the real life test was an epic fail. As I mentioned OPC servers can behave differently. My system was able to read the data’s, but want able to control the process. At the first sight, my control service hasn’t enough privileged to connect the OPC server, but that was easy to solve. Bigger surprise was after the connection the ABB OPC server refused to accept the variables that I give them, even if they was in the correct type. Figured out, with IIS hosted WCF service, I will not able to control the production process, neither matter which kind of type conversions I use nor from the access rights of the service. Went back to the core, and changed a few things, first of all, I trashed out the IIS hosted WCF services and changed them to Windows Service hosted WCF service, finally only one service was responsible to read and write the OPC tags. That’s worked well in my test environment and also the production environment.

Finally comes a few lines of code. I used OPC .net API. I used the DA mode, so codes are related to that.
For discovering OPC servers, there can be done via ServerEnumerator, with the help of OpcEnum service, so make sure it’s running, or you will get an exception.

Opc.IDiscovery discovery = new OpcCom.ServerEnumerator ();
Opc.Server[] localServers = discovery.GetAvailableServers (Opc.Specification.COM_DA_20);
foreach (Opc.Server found in localServers)
{         
 txtServers.AppendText (string.Format ("\r\n{0}", found.Name));
}

For connecting to the server, use the Server class. It will require a Factory class, and the URL of the server. You can check the connection with the IsConnected property. In this example the URL came from the result of the discovery.

private void ConnectToServer (Opc.URL url)
{
 server = new Server (new OpcCom.Factory (), url);
 server.Connect ();
}

You can list the available items:

Opc.ItemIdentifier itemId = null;
BrowsePosition position;
BrowseFilters filters = new BrowseFilters () { BrowseFilter = browseFilter.all };
BrowseElement[] elements = server.Browse (itemId, filters, out position);
foreach (BrowseElement beItem in elements)
{ ... }

With giving a null as an initial ItemIdentifier, the server will give back its items, but be aware, it will list only the first level; items can have more child elements, check with the HasChildren property.

For change notification subscription, you need the tags, which you want to monitor.

Subscription groupRead;
SubscriptionState groupState = new SubscriptionState ();
groupState.Name = "Group1";
groupState.Active = true;
groupState.UpdateRate = updateRate;
groupState.Deadband = 0; // The minimum percentage change required to trigger a data update for an item.
groupRead = (Subscription) server.CreateSubscription (groupState);
groupRead.DataChanged += new DataChangedEventHandler (groupRead_DataChanged);

Item[] itemsYouWantToRead = new Item[n]; //n number of OPC tag
groupRead.AddItems (itemsYouWantToRead);

Although Subscription.AddItems expect an array, it’s still easier to start with a List fill the list with the items, new Item(){ ItemName = tag }; than convert it to an Array. Of course you can remove items from monitoring with the Subscription.RemoveItems, only one difference, which is waiting for an ItemResult array, but the principles are the same, you manage the items trough ItemName. When you add to the subscription, you also get an ItemResult collection, when you remove items you get an IdentifiedResult array, worth to check it.

Finally, to give work with controlled items, you need to change the related item’s value.

ItemValue testWrite = new ItemValue ();
testWrite.ItemName = itemOpcTag; 
testWrite.Value = changeValue;
List<ItemValue> itemsToWrite = new List<ItemValue> ();
itemsToWrite.Add (testWrite);
server.Write (itemsToWrite.ToArray ());

Item subscribtion or simple item read will return an ItemResult array. You can iterate through it. It’s worth check out the result. There are the Quality object, which will show “good” if the given value considered good, or will give some meaningful string, considered about the returned value. Also, worth check out the ResultId, which will start with an “E_” if there are some error happened, but usually the Quality which will provide more information. Even if you try to read a non existing tag, you will get an S_OK for ResultId, but the Quality will show, that item is not exist. It’s simple enough to use them on the ResultId.ToString() and Quality.ToString(). Although writing an item and removing from the subscription will result a different kind of array, you can still check the ResultId.
I don’t think, that I should mention here, like anywhere else, the importance of the log files! It can save you from trouble, like one case on this project. The production process was behaved irregularly, and the logs showed, that all of my commands and results was fine, just was some undocumented OPC tag, which I should give to the OPC server, but it wasn’t in the documentation, so saved our collective…

Advertisements

Search for duplicated files using C# and LINQ

Over the years I downloaded, copied, moved around my files, sometimes I made lot of copies, or put them in different directories. And now, there is a time, to clean up some duplicates. I took the easy way, quickly created a small application, which filtering my drive based on file name and length.
The solution is easy, first I create a FileInfo list, fill the list with FileInfo’s. I walk through the directory tree with recursion, and not bothering myself with permission violation. Than search for duplicates, than I create a file with possible duplicates.
Here is the basic code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security;
using System.Security.Permissions;

namespace ConsoleApplication1
{
    public class DuplicateFileFinderClass
    {
        public static List<FileInfo> files = new List<FileInfo> ();
        public static void ListDrive (string drive, bool enumerateFolders)
        {
            try
            {
                DirectoryInfo di = new DirectoryInfo (drive);
                foreach (FileInfo fi in di.EnumerateFiles ())
                {
                    files.Add (fi);
                }

                if (enumerateFolders)
                {
                    foreach (DirectoryInfo sdi in di.EnumerateDirectories ())
                    {
                        ListDrive (sdi.FullName, enumerateFolders);
                    }
                }


            }

            catch (UnauthorizedAccessException) { }
        }

        public static void ListDuplicates ()
        {
            var duplicatedFiles = files.GroupBy (x => new { x.Name, x.Length}).Where (t => t.Count () > 1).ToList ();

            Console.WriteLine ("Total items: {0}", files.Count);
            Console.WriteLine ("Probably duplicates {0}", duplicatedFiles.Count ());

            StreamWriter duplicatesFoundLog = new StreamWriter ("DuplicatedFileList.txt");

            foreach (var filter in duplicatedFiles)
            {
                duplicatesFoundLog.WriteLine ("Probably duplicated item: Name: {0}, Length: {1}",
                    filter.Key.Name,
                    filter.Key.Length);

                var items = files.Where (x => x.Name == filter.Key.Name &&
                    x.Length == filter.Key.Length).ToList ();

                int c = 1;
                foreach (var suspected in items)
                {
                    duplicatesFoundLog.WriteLine ("{3}, {0} - {1}, Creation date {2}",
                        suspected.Name,
                        suspected.FullName,
                        suspected.CreationTime,
                        c);
                    c++;
                }

                duplicatesFoundLog.WriteLine ();
            }

            duplicatesFoundLog.Flush ();
            duplicatesFoundLog.Close ();
        }
    }
}

From the console application I first call the ListDrive method, than call ListDuplicates method. Well, I don’t say it’s the best and most elegant way, but quickly served my needs. The whole process took around 31 seconds, 6 for compile the list, 25 for create the log, in 500GB HDD, with over 6600 duplications. With less than 100 lines of code.