This article describes how to create a class that has properties and methods that can be used to enhance the functionality of the standard GalilTools API in a .NET environment. The default API provides a good base communication shell that allows the user to connect to a controller, send commands, upload/download programs, upload/download arrays, and work with the controllers built in memory map (called the Data Record). However, for more advanced functions - the user can implement a Class that adds a layer of customization to the bare API. For example, to access the value of analog input 1 on the controller, the simplest approach is to do the following:
Label1.Text=g.commandValue("MG@AN[1]")
Where g represents an object that holds the "Galil" class from the GalilTools Class Library. This will populate Label1 with the value of the response to the command that was sent. Similarly, the A axis actual position could be read using a similar structure:
Label1.Text=g.commandValue("TPA")
so it is possible to quickly and easily get access to any Galil command using the g.commandValue( _ ) format. However, it is also nice to get to the same information using a call such as:
Label1.Text = r.AnIn[1]
So , for more complex applications where there could be numerous queries to many different commands - it may be desirable to streamline communication - and therefore it is best to grab the information in one big "snapshot" and then parse that information out as opposed to grabbing each individual item one at a time. Enter the controllers Data Record - this is similar to a memory map of the controller that can be sent out of the controller as one big packet and then leverages the software running on the PC to provide access to the desired information. In this way, the communication can be made more efficient by requiring less individual queries for items that are contained in the Data Record.
A second problem with the first method shown above is that it requires the programmer to get the command syntax correct every time they request a piece of information. If they enter the wrong string for the g.command( _ ) call, then the program will fail at runtime - but there is no indication that anything is wrong when writing the program. To solve these issues, a custom "Class" library can be used that packages the programmers functions and data requests into a more manageable format. The goal of the class will be to create a method that will allow us to get the DataRecord information into an object and also to create a property that has read/write capability. As an example we will be working with the RIO hardware but the same type of programming can be applied to any Galil controller.
We'll use the C# programming language but the same idea applies for other languages as well. Open Visual Studio and create a new Class Library file. Visual Studio starts out with a namespace, a class called Class1 and the constructor called Class1( ). The "constructor" Class1( ) will run when the class is first created - it can be thought of as the initialization routine. Keep this in mind as we will refer to it later in the article.
There are three main things that are used in classes - methods, properties, and events. Methods are essentially subroutines that can have a return value and optionally a list of parameters that can be passed in and out of the routine. Properties are essentially variables or values that are stored as part of the class. Lastly - events can be created in order to alert the main program when something happens. In this article we're going to stick with the first two - methods and properties.
So, after all that - lets get to something useful. The goal here is to write a class that will allow access to the RIO Data Record. This way, the information in the data record can be read in a simpler format than specifying the Galil command every time the data is requested. Here is an example of how we will be grabbing the sample time from the data record in the new format:
textBox1.Text = r.SampleNumber.ToString();
(note: the .ToString() is to convert the value from an integer to a string for display purposes)
First, I'll show the entire class definition and then we will break it up and discuss each section. Here's the entire class:
using System; using System.Text; namespace RIO_DataRecord_namespace { /// <summary> /// RIO_DataRecord_Class gives access to the RIO DataRecord /// </summary> public class RIO_DataRecord { public uint Header; public ushort SampleNumber; public byte ErrorCode; public byte GenSatus; public ushort[] AnOut; public ushort[] AnIn; public ushort DOut; public ushort DIn; public uint PulseCount; public int ZcData; public int ZdData; private string zcdata=""; const int RIO_RECORD_BYTES = 56; const int RIO_ANALOGIO_CHANNELS = 8; private Galil.Galil g; public RIO_DataRecord(ref Galil.Galil g_passed) { g=g_passed; GetRecord(g); } public void GetRecord(Galil.Galil g) { Byte[] b = new Byte[RIO_RECORD_BYTES]; try { String s = ""; g.write("QR\r"); while (s == "") { s = g.read(); } b = ASCIIEncoding.Default.GetBytes(s); packRioRecord(b); } catch { //throw error } } void packRioRecord(Byte[] b) { Header = BitConverter.ToUInt32(b, 0); SampleNumber = BitConverter.ToUInt16(b, 4); ErrorCode = b[6]; GenSatus = b[7]; for (int i = 0; i < RIO_ANALOGIO_CHANNELS; i++) { AnOut[i] = BitConverter.ToUInt16(b, 8 + (2 * i)); } for (int i = 0; i < RIO_ANALOGIO_CHANNELS; i++) { AnIn[i] = BitConverter.ToUInt16(b, 24 + (2 * i)); } DOut = BitConverter.ToUInt16(b, 40); DIn = BitConverter.ToUInt16(b, 42); PulseCount = BitConverter.ToUInt32(b, 44); ZcData = BitConverter.ToInt32(b, 48); ZdData = BitConverter.ToInt32(b, 52); } public string ZC { get { string str="MG_ZC"; zcdata=g.command(str,"\r",":",true); return zcdata; } set { string str="ZC"+value; g.commandValue(str); zcdata=value; } } } }
We'll start from the top. To get the Data Record from the RIO, we first have to create the map for the data that gets returned from the QR command. The QR command returns a packet of binary data that we will parse to get specific information. To parse the information, we'll need to use some of the built in functions of .NET that are contained in the System.Text namespace.
So we first add the System.Text namespace at the top of the code. Next, you will need to add the variable definitions above the "constructor" method after the RIO_DataRecord class definition as shown below. The size of each variable is taken from Chapter 3 of the RIO User Manual that explains how many bytes are contained in each piece of data.
using System; using System.Text; namespace RIO_DataRecord_namespace { /// <summary> /// RIO_DataRecord_Class gives access to the RIO DataRecord /// </summary> public class RIO_DataRecord { public uint Header; public ushort SampleNumber; public byte ErrorCode; public byte GenSatus; public ushort[] AnOut; public ushort[] AnIn; public ushort DOut; public ushort DIn; public uint PulseCount; public int ZcData; public int ZdData; private string zcdata=""; const int RIO_RECORD_BYTES = 56; const int RIO_ANALOGIO_CHANNELS = 8; private Galil.Galil g;
Next we can add code to the constructor which as you will recall is what runs when the class is created. In this case we will need to pass the Galil object that gets created by the main program so that we can use the previously opened connection to send a command and get a response from the controller. To do this, use the following constructor:
public RIO_DataRecord(ref Galil.Galil g_passed) { g=g_passed; GetRecord(g); }
Now, lets make the GetRecord( ) function that handles sending the QR command and then getting the response. Note that the .read and .write method of getting the QR info is a very specific case when dealing with the binary response from the controller - in all other cases, the g.command or g.commandValue should be used to send/receive commands to the controller. Also the while loop is not necessary when using the g.command or g.commandValue and therefore those functions will speed up communication compared to the .read and .write methods.
public void GetRecord(Galil.Galil g) { Byte[] b = new Byte[RIO_RECORD_BYTES]; try { String s = ""; g.write("QR\r"); while (s == "") { s = g.read(); } b = ASCIIEncoding.Default.GetBytes(s); packRioRecord(b); } catch { //throw error } }
Notice there is a function that is called packRioRecord(b) inside the above method. Next, we will create that function so that the Data Record packet that was retrieved can be populated into the correct format. Here is the function that does this:
void packRioRecord(Byte[] b) { Header = BitConverter.ToUInt32(b, 0); SampleNumber = BitConverter.ToUInt16(b, 4); ErrorCode = b[6]; GenSatus = b[7]; for (int i = 0; i < RIO_ANALOGIO_CHANNELS; i++) { AnOut[i] = BitConverter.ToUInt16(b, 8 + (2 * i)); } for (int i = 0; i < RIO_ANALOGIO_CHANNELS; i++) { AnIn[i] = BitConverter.ToUInt16(b, 24 + (2 * i)); } DOut = BitConverter.ToUInt16(b, 40); DIn = BitConverter.ToUInt16(b, 42); PulseCount = BitConverter.ToUInt32(b, 44); ZcData = BitConverter.ToInt32(b, 48); ZdData = BitConverter.ToInt32(b, 52); }
The last thing to do is to show how to create a property instead of a method as shown above. Properties do not take input parameters - however they can have a value set or a value read. The code below shows how to create a new property called ZC that will set a value for the ZC value in the data record or will retrieve the value that is currently stored.
public string ZC { get { string str="MG_ZC"; zcdata=g.command(str,"\r",":",true); return zcdata; } set { string str="ZC"+value; g.commandValue(str); zcdata=value; } } } }
and that finishes the class. This example shows how to create properties and methods for a custom user class that can extend the capabilities of the GalilTools programming interface. From here, it is easy to see how one could create a class with custom routines and properties that allow for high level methods and properties.
Save this file and watch for Part 2 of this article which explains how to reference a class inside a C# program.