OPC Client c# .Net Sample (RSLinx OPC server)

Hi, a big problem about connecting PLC to PC is the "from where should i start?".
And that's why i suppose people choose commercial SCADA instead of .Net (they already has drivers and so on).
Well, if it was for winforms, i would agree that winforms sucks, they got a lot of bugs and so on, so i would choose a commercial SCADA too.
But since WPF and Silverlight was released, .Net is really more more powerful and beautiful, the code became easier with the binding system and industrial PCs + OPC servers cost always less (and sometimes it's possible to find free drivers).

This post is an example of communication with RSLinx OPC Server (but i use OPC Foundation libraries so the process is the same for every OPC server).


So let's strart by adding a reference to the OPC Foundation dlls:
- OpcNetApi.Com.dll
- OpcNetApi.dll
- OpcNetApi.Xml.dll

Then create Items that are shared for the all the form (or the project)
Then create a button to connect (or just place the code after InitializeComponent(); if you want to autoconnect when the program starts)
Set the URL (in this sample there is the one of RSLinx) then add a group, add some items with theyr connection string, set the update time and the callback for the items that you want to read, then set a group for writings.


 This is the sample callback where i assign values that i read to a custom object.
Remember that the GUI and the OPC client runs on two different threads, so it's needed to use anonymous method for this (just use like is written in the sample).
With WPF and INotifyPropertyChanged it's not needed this.
You can just bind all your objects to the GUI items property, and this is really amazing and simple.
 This is the way that i found to write a single word, basically i create a new one, i check if in the write group there is already one of them, else i add it.
 Writing was always a problem for me, but when i found this way, i got all i needed

This is a sample of how i write from a button reading a textbox


IMPORTANT UPDATE: This code is written on a Micrologix / SLC platform and it reads only integer values, so when i read a value it's supposed to be a 16-bit word: that's why i cast it to (short). Remember that if you read a REAL value (F8:0 for example) you sould cast it to float and if you read from Controllogix or CompactLogix, you have to convert it to (int) because they are 32-bit words.

This is the complete solution:

http://www.mesta-automation.com/Downloads/OPC%20Client.rar

This is another link for a Twincat OPC Server, that is similar to the RSLinx.
http://infosys.beckhoff.com/content/1033/tcopcserver/html/sample1_netapi.htm?id=18186

UPDATE: i wrote a recent post here where i wrote a full sample with WPF and an OPC client in c#. watch it here: http://mestaa.blogspot.com/2010/12/wpf-and-opc-full-sample-project-with.html

[UPDATE #2]: i made a video that explains how to use the code provided: check it here: http://www.mesta-automation.com/opc-client-with-c-an-how-to-video/

36 comments:

Alysson said...

I copy the dll files OpcNetApi.Com.dll, OpcNetApi.dll and OpcNetApi.Xml.dll that you provide as "OPC libraries" to directory Debug\bin. When I try to run your program, VisualStudio inform that can't load 'OpcRcw.Dx, Version=1.0.1.21'! To solve this problem I need to copy the dll files OpcRcw.Ae.dll, OpcRcw.Comn.dll, OpcRcw.Da.dll, OpcRcw.Dx.dll, OpcRcw.Hda.dll, OpcRcw.Mon.dll to directory Debug\bin. Why? Where did you find the OPC.Net API? Are you a OPC Foundation member? Thanks in advance. Alysson (alysson.r.n@hotmail.com)

Michele Cattafesta said...

I found those 3 files in the free version of RSLinx, (RSLinx lite) but
i think that there are a lot of programs that provides those libraries inside their folders, and i added them to give a working example.
The 3 dlls that i provide are not the latest version, but they works and the only purpose of adding them was to give a working example.
If you like OPC connectivity and you want the latest version or you want to read the full documentation with source code, you need to be a member of OPC Foundation.

Alysson said...

Could you please verify if the RSLinx copy the mentioned dlls (OpcRcw.Ae.dll, OpcRcw.Comn.dll, OpcRcw.Da.dll, OpcRcw.Dx.dll, OpcRcw.Hda.dll, OpcRcw.Mon.dll) to your computer (maybe in c:\%windir%\system32). Do you already test this program in a computer that doesn´t have the RSLinx installed?
Alysson

Michele Cattafesta said...

It copied them, just run a search in windows.
This libraries should work for every OPC server and should be provided by every OPC Server, because OPC is a standard.
I suppose that RSLinx is based on them to read and write from AB plcs.
But still i don't understand what's your problem, if you need more explanation there is the forum of OPC Foundation.
At the bottom of the post there is a link to the code to communicate with the Twincat Plc with the same libraries, and if you watch it just changes the names to access variables.
But if you have an OPC server (like RSLinx classic is) there should be a way to "browse" the variables, getting the full path to access to PLC's values.
Unfortunately mine is an easy example that just read and writes.

Michele Cattafesta said...

...
This libraries MUST* work for every OPC server and should be provided by every OPC Server, because OPC is a standard.

Anonymous said...

Your tutorial is an excellent job, i can now read values from PLC, but it only works with addresses that you use in the tutorial, when a tried to use mines it doesnt works, can you help me? maybe there is a trick to make the reference to the address?

Thanks.

Anonymous said...

Problem Found, changed short to float and now works fine, Cheking in Rslinx found that FX:X variables were Float not short, NX:X were Short.

Thanks

Keith said...

Do you have any example code showing how to browse tags on an OPC server and show them in a tree view?

Michele Cattafesta said...

No i don't have any example for that.
Usually to browse variables i use RsLinx OPC Test Tool, that it's included with every RsLinx package.

Anonymous said...

Hi Mestaa, thanks for this im now able to read data from PLC, im working on a little project to read some data and put it in a my MySQL database and in a excel file every hour, but i cant find the way to successfully disconnect and reconnect the client without an error. can you put me in the right way to do this?

Thanks for your help.

Michele Cattafesta said...

Can you tell me what errors you receive? I will make some tests in the weekend to see if i can reproduce the error and find a solution.

João Mello said...

Excellent job guy !!!
Post more about!!!


From Brazil!

Anonymous said...

thanks for this,

Do you know a way to do this on python? i cant find how to pull data from rslinx in python.

thanks

Michele Cattafesta said...

You can use this library to write your client in python:
http://openopc.sourceforge.net/api.html

Anonymous said...

thanks for answering, i already tried openopc, but is outdated it only supports python2.5, i was looking for something more straight forward like in your c# code.

Anonymous said...

I know this is an old topic, but if you need to install the OPCRcw files, go to the opc foundation website and download the OPC Core Components 2.00 SDK. This is a free download if you sign up.

Salih said...

You are awesome. Thanks to these codes i saved a lot of time. Keep up the awesome work ;) Thank you.

Max_nde said...
This comment has been removed by the author.
Max_nde said...

Grazie per il il codice cha hai scritto, e da tempo che stavo cercando un esempio "pratico".
Sto provando ad implementarlo in un sito web, riesco a leggere i dati dal plc, ma a differenza del tuo esempio i dati si aggiornano solo quando clikko sul tasto "connect". Credo sia dovuto al fatto che non riesco a richiamare il metodo "group_DataChanged". Non posso usare "lblN7Read.Invoke". Hai qualche suggerimento?
Grazie.
Massimo

mesta said...

Ciao. Il codice che ho scritto non è per siti, ma per applicazioni desktop.
La differenza sta nel fatto che un sito è un'architettura client-server, mentre un'applicazione è standalone con tutto incluso in se stessa.
L'opc client, in un sito, dovrebbe essere contenuto nel server ed sempre connesso al plc, in modo che il client, quando si connette al server, visualizza i dati in tempo reale.
Da quello che mi pare di capire te invece hai messo il codice per accedere all'opc server direttamente nel client, per cui devi fare il refresh della pagina per aggiornare i dati.

Max_nde said...

Ciao, grazie per aver risposto.
L'Opc client è contenuto direttamente nel server. Questo server è sempre connesso a diversi plc tramite RsLinx (come servizio) per fare delle raccolte dati. Ho creato un sito Asp.net su quel server, in modo che gli utenti possano vedere dati di produzione, allarmi ecc. Ciò che mi piacerebbe fare è quello di aggiungere una pagina su quel sito, tipo una specie di "sinottico" che facesse vedere lo stato delle linee di produzione in tempo reale.
Se hai tempo, e se non ti disturbo troppo, domani posso postarti il codice.
Grazie ancora. ciao.
Massimo

mesta said...

Ok dai posta il codice, anche se penso che un sito complichi le cose non di poco, visto che il ciclo vita di una pagina web non è banale.

Max_nde said...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using RsLinx_OPC_Client;

namespace RsLinx_OPC_Client
{
class MyOPCObject
{
public short[] DataN7 { get; set; }
public short[] DataN11 { get; set; }
public short[] BitsB3 { get; set; }
public short[] BitsB10 { get; set; }

}
}




public partial class _Default : System.Web.UI.Page
{
#region Variables for OPC client
//ho tolto i groupWrite e groupStateWrite. per ora mi interessa solo leggere le variabili da PLC
Opc.URL url;
Opc.Da.Server server;
OpcCom.Factory fact = new OpcCom.Factory();
Opc.Da.Subscription groupRead;
Opc.Da.SubscriptionState groupState;
Opc.Da.Item[] items = new Opc.Da.Item[1];
#endregion

MyOPCObject myOpcObject = new MyOPCObject();

public void Page_Load(object sender, EventArgs e)
{

}

protected void Timer1_Tick(object sender, EventArgs e)//timer usato per il refresh dell'Ajax UpdatePanel
{
//questi 4 oggetti sono all'interno dell'UpdatePanel. Se la variabile N7 del PLC cambia l'utente la vede //cambiare senza che tutta la pagina si aggiorni dando la sensazione simile ad uno SCADA.

Label1.Text = string.Format("{0:F}", DateTime.Now);
GridView1.DataBind();
lblN7_0_Read.DataBind();
lblN7_1_Read.DataBind();
}

protected void plcConn_Click(object sender, EventArgs e)
{
// 1st: Create a server object and connect to the RSLinx OPC Server
url = new Opc.URL("opcda://localhost/RSLinx OPC Server");
server = new Opc.Da.Server(fact, null);
server.Connect(url, new Opc.ConnectData(new System.Net.NetworkCredential()));

//3rd Create a group if items
groupState = new Opc.Da.SubscriptionState();
groupState.Name = "Group";
groupState.UpdateRate = 10;// this isthe time between every reads from OPC server
groupState.Active = true;//this must be true if you the group has to read value
groupRead = (Opc.Da.Subscription)server.CreateSubscription(groupState);
groupRead.DataChanged += new Opc.Da.DataChangedEventHandler(group_DataChanged);//callback when the data are readed

// add items to the group (in Rockwell names are identified like [Name of PLC in the server]Block of word:number of word,number of consecutive readed words)
Opc.Da.Item[] items = new Opc.Da.Item[1];
items[0] = new Opc.Da.Item();
items[0].ItemName = "[CENGRA]N7:30,L2";//this reads 2 word (short - 16 bit)
items = groupRead.AddItems(items);


}

//it's good to remember that if you update a variable every 1 second, this will be a good timer to use also for writings
void group_DataChanged(object subscriptionHandle, object requestHandle, Opc.Da.ItemValueResult[] values)
{
for (int i = 0; i < values.Length; i++)
{
short[] receivedData = (short[])values[i].Value;
if (values[i].ItemName == "[CENGRA]N7:30,L2")
{
myOpcObject.DataN7 = receivedData;

//remember that it's in another thread (so if you want to update the UI you should use anonyms methods)

//è in questo punto che non so come risolvere. Non posso usare le lambda expression per costruire un metodo //anonimo. Le variabili "lblN7_0_Read" e "lblN7_1_Read", vengono lette correttamente, ma solo quando clikko //sul "plcConn" button


//lblN7Read.Invoke(new EventHandler(delegate { lblN7Read.Text = myOpcObject.DataN7[0].ToString(); }));
lblN7_0_Read.Text = myOpcObject.DataN7[0].ToString();
lblN7_1_Read.Text = myOpcObject.DataN7[1].ToString();
}
}
}
}

Michele Cattafesta said...

Quando clicchi la callback arriva istantanea perche' il server te la manda subito.
Dovresti controllare se quando il timer fa tick sei ancora connesso all'opc server, o se la connessione muore dopo il click del pulsante.
Per collegare le variabili potresti creare delle variabili statiche che aggiorni all'interno della callback dell'opc.
Poi imposti il valore della textbox = a quello della variabile statica all'interno del timer_tick.
Eventualmente ci do un'occhiata stasera.
Ciao.

Max_nde said...

Ciao.
Quando il timer fa tick sono ancora connesso all'opc server, ed è anche questo il problem: ogni volta che l'utente clikka su tasto l'opc mi apre una nuova connessione, non chiudendomi quella precedente. Per ora ho risolto in questo modo, sembra funzionare abbastanza bene, dimmi tu se devo cambiare qualcosa:

Max_nde said...

namespace RsLinx_OPC_Client
{
class MyOPCObject
{
public float[] Livelli_Dep { get; set; }
//public short[] DataN11 { get; set; }
//public short[] BitsB3 { get; set; }
//public short[] BitsB10 { get; set; }

}
}


public partial class _Default : System.Web.UI.Page
{
#region Variables for OPC client

Opc.URL url;
Opc.Da.Server server;
OpcCom.Factory fact = new OpcCom.Factory();
Opc.Da.Subscription groupRead;
Opc.Da.SubscriptionState groupState;
Opc.Da.Item[] items = new Opc.Da.Item[1];
#endregion

MyOPCObject myOpcObject = new MyOPCObject();

public void Page_Load(object sender, EventArgs e)
{
updateItems();
}

protected void Timer1_Tick(object sender, EventArgs e)
{
Label1.Text = string.Format("{0:F}", DateTime.Now);
//GridView1.DataBind();
//lbl_Read0.DataBind();
//lbl2_Read.DataBind();
updateItems();
}
protected void updateItems()
{
// 1st: Create a server object and connect to the RSLinx OPC Server
url = new Opc.URL("opcda://localhost/RSLinx OPC Server");
server = new Opc.Da.Server(fact, null);
server.Connect(url, new Opc.ConnectData(new System.Net.NetworkCredential()));

//3rd Create a group if items
groupState = new Opc.Da.SubscriptionState();
groupState.Name = "MixProfumi";
groupState.UpdateRate = 1000;// this isthe time between every reads from OPC server
groupState.Active = true;//this must be true if you the group has to read value
groupRead = (Opc.Da.Subscription)server.CreateSubscription(groupState);


//groupRead.DataChanged += new Opc.Da.DataChangedEventHandler(group_DataChanged);//callback when the data are readed

// add items to the group (in Rockwell names are identified like [Name of PLC in the server]Block of word:number of word,number of consecutive readed words)
Opc.Da.Item[] items = new Opc.Da.Item[1];
items[0] = new Opc.Da.Item();
items[0].ItemName = "[MixProfumi]Livelli_Dep[0],L50";//this reads 2 word (short - 16 bit)
items = groupRead.AddItems(items);
Opc.Da.ItemValueResult[] values = groupRead.Read(items);

for (int i = 0; i < values.Length; i++)
{
float[] receivedData = (float[])values[i].Value;
if (values[i].ItemName == "[MixProfumi]Livelli_Dep[0],L50")
{
myOpcObject.Livelli_Dep = receivedData;

TextBox1.Text = myOpcObject.Livelli_Dep[1].ToString() + " Q.li";
TextBox2.Text = myOpcObject.Livelli_Dep[2].ToString() + " Q.li";
//proseguo cosi fino alla TextBox49


server.Dispose();
}
}
}
}

Michele Cattafesta said...

Mi pare che vada bene.
Eventualmente prova a spostare la connessione al server quando apri al pagina e a disconnetterti quando la chiudi.

Tipo cosi':
namespace RsLinx_OPC_Client
{
class MyOPCObject
{
public float[] Livelli_Dep { get; set; }
}
}


public partial class _Default : System.Web.UI.Page
{
#region Variables for OPC client

Opc.URL url;
Opc.Da.Server server;
OpcCom.Factory fact = new OpcCom.Factory();
Opc.Da.Subscription groupRead;
Opc.Da.SubscriptionState groupState;
Opc.Da.Item[] items = new Opc.Da.Item[1];
#endregion

MyOPCObject myOpcObject = new MyOPCObject();

public void Page_Load(object sender, EventArgs e)
{
// 1st: Create a server object and connect to the RSLinx OPC Server
url = new Opc.URL("opcda://localhost/RSLinx OPC Server");
server = new Opc.Da.Server(fact, null);
server.Connect(url, new Opc.ConnectData(new System.Net.NetworkCredential()));

//3rd Create a group if items
groupState = new Opc.Da.SubscriptionState();
groupState.Name = "MixProfumi";
groupState.UpdateRate = 1000;// this isthe time between every reads from OPC server
groupState.Active = true;//this must be true if you the group has to read value
groupRead = (Opc.Da.Subscription)server.CreateSubscription(groupState);

updateItems();
}

protected void Timer1_Tick(object sender, EventArgs e)
{
Label1.Text = string.Format("{0:F}", DateTime.Now);
updateItems();
}

protected void updateItems()
{
// add items to the group (in Rockwell names are identified like [Name of PLC in the server]Block of word:number of word,number of consecutive readed words)
Opc.Da.Item[] items = new Opc.Da.Item[1];
items[0] = new Opc.Da.Item();
items[0].ItemName = "[MixProfumi]Livelli_Dep[0],L50";//this reads 2 word (short - 16 bit)
items = groupRead.AddItems(items);
Opc.Da.ItemValueResult[] values = groupRead.Read(items);

for (int i = 0; i < values.Length; i++)
{
float[] receivedData = (float[])values[i].Value;
if (values[i].ItemName == "[MixProfumi]Livelli_Dep[0],L50")
{
myOpcObject.Livelli_Dep = receivedData;

TextBox1.Text = myOpcObject.Livelli_Dep[1].ToString() + " Q.li";
TextBox2.Text = myOpcObject.Livelli_Dep[2].ToString() + " Q.li";
//proseguo cosi fino alla TextBox49
}
}
}

protected void Page_Closing() //web page is going to be closed
{
if(server.IsConnected)
server.Disconnect();

if(server != null)
server.Dispose(); //se serve
}
}

Max_nde said...

Grazie mille per la tua collaborazione, sei stato davvero molto disponibile. Lunedì che torno al lavoro proverò senz'altro.
Ciao e buon Week-end.

Massimo

Michele Cattafesta said...

Fammi sapere come va.
Ciao :)

Max_nde said...

Ciao Michele.
Ho provato, funziona perfettamente, anche meglio di come l'avevo fatto io:-)
Non ho capito solo come fai a leggere un singolo bit da PLC (nel caso dovessi usarlo per "colorare" una label).
Nel codice hai messo:
[UNTITLED_1]B3:0,L2;//this read a 2 word array (but in the plc the are used as bits so you have to mask them)
Grazie ancora.
Ciao

Michele Cattafesta said...

Devi usare delle operazioni sui bit: (http://www.codeproject.com/Articles/1544/Bit-wise-operations-in-C)

Esempio B3:0/9 = true e gli altri a false.
B3:0 = 256, shifti di 9 a destra e fai and con 1 e ottieni se è true o false.
public static bool GetBool(int position)
{
bool value = (myInteger >> 1) & 1;
return value;
}

Anonymous said...

Hi Mesta,
I got this error when trying to connect to OPC server:
Could not load file or assembly 'OpcRcw.Dx, Version=1.0.1.21, Culture=neutral, PublicKeyToken=9a40e993cbface53' or one of its dependencies. The system cannot find the file specified.

I had copy the dll files OpcRcw.Ae.dll, OpcRcw.Comn.dll, OpcRcw.Da.dll, OpcRcw.Dx.dll, OpcRcw.Hda.dll, OpcRcw.Mon.dll to directory Debug\bin but still there was error about could not load the opcRcw.dx
I also tried to copy the mentioned dlls (OpcRcw.Ae.dll, OpcRcw.Comn.dll, OpcRcw.Da.dll, OpcRcw.Dx.dll, OpcRcw.Hda.dll, OpcRcw.Mon.dll) to my computer (in c:\%windir%\system32) but still had an error

I used yous code but I used KepServerEx as the OPC server.
I have change the url to kepserver.

mshah said...

Hi,

I have tried provided the solution. I have installed RSLinx Classic Lite on my windows XP machine and solution given I have tried using windows 7. I am facing the same issue as can't load 'OpcRcw.Dx, Version=1.0.1.21'. I tried searching for below libraries on windows xp machine so that I can copy those to my windows 7 computer.

OpcRcw.Ae.dll, OpcRcw.Comn.dll, OpcRcw.Da.dll, OpcRcw.Dx.dll, OpcRcw.Hda.dll, OpcRcw.Mon.dll

I couldn't find above libraries. Where can I find these libraries?
OpcRcw.Ae.dll, OpcRcw.Comn.dll, OpcRcw.Da.dll, OpcRcw.Dx.dll, OpcRcw.Hda.dll, OpcRcw.Mon.dll

Thank you,
Mayur Shah
+91 99402 28234

Michele Cattafesta said...

What you need is Core component.
OPC Core Components 3.00 Redistributable (x86) from http://www.opcfoundation.org/Downloads.aspx?CM=1&CN=KEY&CI=286

Mayur Shah said...

Hi Michele Cattafesta,

Thank you so much for your valuable reply.
Let me try it and give you feedback.

Thank you,
Mayur Shah

Unknown said...

There is a constructor parameter named useRemoting for Opc.Factory(from which OpcCom.Factory inherits). I guess it is about the technology used for communicating with remote servers... Do you know what difference does it introduce if set "true"?