OPC Updater Client
This section explains how to implement a tag entry/exit protocol in OPC clients.
The following screenshot shows an OPC server tag value, containing two Ubisense tag IDs, which has been set automatically with the form used in the OPCExample client. Note that this server browser shows the tag values as decimal numbers rather than the hexadecimal formats that most Ubisense programs use.
OPC Server Interface Code
There are two clients:
- A client for updating this tag value with entry/exit protocol information.
- A client for reading the value back from the server.
The clients require an OPC server to connect to, with an appropriate OPC item for storing Ubisense tag IDs.
The following code example shows how to interface the entry/exit protocol with an OPC server, using the OPC Foundation’s Classic .NET API Redistributables. The clients use this code to connect to OPC server and provide read/write values.

/// <summary> /// An OPC DA connection to an OPC server for reading and writing values. One each item has its own group /// to account for mixing active/inactive items. /// </summary>
public class OpcDaServer { public delegate void DataChangedEventDelegate(object value); private Opc.Da.Server Server; private Dictionary<string, Opc.Da.Subscription> Groups; public OpcDaServer(string url, NetworkCredential credential) { OpcCom.Factory factory = new OpcCom.Factory(); Server = new Opc.Da.Server(factory, null); Server.Connect(new Opc.URL(url), new Opc.ConnectData(credential)); Groups = new Dictionary<string, Opc.Da.Subscription>(); } /// <summary> /// Create an inactive subscription group for an item. It will not react to updates on the server and /// is simply an interface for reading/writing the item value. /// </summary> /// <param name="item">The item name</param> /// <returns>Whether a new group was created</returns> public bool CreateNonSubscriptionGroup(string item) { if (Groups.ContainsKey(item)) { return false; } else { Opc.Da.SubscriptionState state = new Opc.Da.SubscriptionState(); state.Name = item; state.Active = false; Groups[item] = (Opc.Da.Subscription)Server.CreateSubscription(state) as Opc.Da.Subscription; Opc.Da.Item[] items = new Opc.Da.Item[1]; items[0] = new Opc.Da.Item(); items[0].ItemName = item; Groups[item].AddItems(items); return true; } } /// <summary> /// Create a subscription group for an item. The group will react to changes on the server and send updated /// values to the delegate provided. /// </summary> /// <param name="item">The item name</param> /// <param name="updateRate">The update rate</param> /// <param name="keepAlive">The keepalive duration</param> /// <param name="dataChanged">Delegate to call when value changes</param> /// <returns>Whether a new group was created</returns> public bool CreateSubscriptionGroup(string item, int updateRate, int keepAlive, DataChangedEventDelegate dataChanged) { if (Groups.ContainsKey(item)) { return false; } else { Opc.Da.SubscriptionState state = new Opc.Da.SubscriptionState(); state.Name = item; state.Active = true; state.UpdateRate = updateRate; state.KeepAlive = keepAlive; Groups[item] = Server.CreateSubscription(state) as Opc.Da.Subscription; Groups[item].DataChanged += new Opc.Da.DataChangedEventHandler((object subscriptionHandle, object requestHandle, Opc.Da.ItemValueResult[] values) => { dataChanged(values[0].Value); }); Opc.Da.Item[] items = new Opc.Da.Item[1]; items[0] = new Opc.Da.Item(); items[0].ItemName = item; Groups[item].AddItems(items); return true; } } /// <summary> /// Check if a subscription group already exists for a given item. /// </summary> /// <param name="item">The item name</param> /// <returns></returns> public bool ContainsSubscriptionGroup(string item) { return Groups.ContainsKey(item); } /// <summary> /// Write an item value to the server. A (non)subscription group should be created for the item first. /// </summary> /// <param name="item">The item name</param> /// <param name="value"></param> /// <returns></returns> public Opc.ResultID Write(string item, object value) { Opc.Da.ItemValue[] items = new Opc.Da.ItemValue[1]; items[0] = new Opc.Da.ItemValue(); items[0].ServerHandle = Groups[item].Items[0].ServerHandle; items[0].Value = value; return Groups[item].Write(items)[0].ResultID; } /// <summary> /// Read an item value from the server. A (non)subscription group should be created for the item first. /// </summary> /// <param name="item">The item name</param> /// <returns></returns> public Opc.Da.ItemValueResult Read(string item) { return Groups[item].Read(Groups[item].Items)[0]; } }
OPC Example Client Code
The following screenshot shows the OPCExample client running. The client listens for entry/exit protocol connections and updates the value of the OPC server item with all Ubisense tag IDs currently matching a recipe.
The following code shows how to use the above OPC Server Interface Code to update an item, gracefully handling new sessions and broken connections from the reader. This code is used in conjunction with the entry/exit protocol, through the event handlers, in the main program.

/// <summary> /// Class to handle updating of (an array of 64bit unsigned integer) value for a /// particular OPC tag. Keeps track of last value sent to server to avoid unnecessary /// updates (assumes this is the only client setting that value). Wait for the whole /// dump before updating the server value on new sessions. /// </summary>
class OPCValueUpdater { OpcDaServer Server; string TagName; HashSet<ulong> CurrentValue; bool GotSessionDump; HashSet<ulong> DumpValue; // Connect to server and check if the tag exists. Throws exceptions when there are // errors with connecting or tag. public OPCValueUpdater(string url, NetworkCredential credential, string tag) { Server = new OpcDaServer(url, credential); TagName = tag; Server.CreateNonSubscriptionGroup(TagName); var value = Server.Read(TagName).Value as ulong[]; if (value != null) CurrentValue = new HashSet<ulong>(value); else CurrentValue = new HashSet<ulong>(); GotSessionDump = false; DumpValue = new HashSet<ulong>(); } // Update the server value. Postpone updating value during the initial dump. public void UpdateValue(IEnumerable<ulong> value) { if (!GotSessionDump) { DumpValue.UnionWith(value); return; } SendToServer(value.ToArray()); } // Get ready for new dump. public void StartSessionDump() { DumpValue.Clear(); GotSessionDump = false; } // The inital dump has ended, send the value to the server and begin normal operations. public void EndSessionDump() { if (GotSessionDump) return; GotSessionDump = true; SendToServer(DumpValue); DumpValue.Clear(); } // Send a value to the server. Value is not sent when there is no change from the previously // sent value. private void SendToServer(IEnumerable<ulong> value) { if (CurrentValue.SetEquals(value)) return; Console.WriteLine("Updating server value to \"{0}\"", string.Join(", ", value.Select(x => string.Format("{0:X}", x)).ToArray())); CurrentValue = new HashSet<ulong>(value); Server.Write(TagName, value.ToArray()); } }
In the main program, the above client code (Part 1) is used in conjunction with the entry/exit protocol, to:
- Create a connection to an OPC server for a specific item using the client code (Part 1).
- Create a protocol object and add event handlers which use this server connection.
- Use the protocol object to create an entry/exit protocol server, and then run the server.

static void Main(string[] args) { // Get configuration. // // …configuration code here… // // Connect to OPC server OPCValueUpdater serverConnection; try { serverConnection = new OPCValueUpdater(opcServerUrl, credential, tagName); } catch (Opc.ConnectFailedException) { Console.WriteLine("Could not connect to OPC server"); return; } catch (IndexOutOfRangeException) { Console.WriteLine("OPC tag does not exist"); return; } // Store of the set of tags currently matching any recipe. ActiveTagStore activeTags = new ActiveTagStore(); // Create a protocol object. EntryExitProtocol protocol = new EntryExitProtocol(); // Add protocol event handlers. EntryExitProtocol.ProtocolEntryOrExitHandler entryDelegate = (id1, id2, tags) => protocol_OnEntry(serverConnection, activeTags, id1, id2, tags); EntryExitProtocol.ProtocolEntryOrExitHandler exitDelegate = (id1, id2, tags) => protocol_OnExit(serverConnection, activeTags, id1, id2, tags); EntryExitProtocol.ProtocolEventHandler startDelegate = () => protocol_OnSessionStart(serverConnection, activeTags); EntryExitProtocol.ProtocolEventHandler keepaliveDelegate = () => protocol_OnKeepalive(serverConnection); protocol.OnEntry += entryDelegate; protocol.OnExit += exitDelegate; protocol.OnSessionStart += startDelegate; protocol.OnKeepalive += keepaliveDelegate; // Create a server. EntryExitServer server = new EntryExitServer(clientAddress, clientPort, protocol); // Run the server. server.Start(); // Wait forever. while (true) System.Threading.Thread.Sleep(1000); } #region Protocol methods // Log the added tags on the command line and send the updated value to the OPC server. static void protocol_OnEntry(OPCValueUpdater valueUpdater, ActiveTagStore activeTags, ushort id1, ushort id2, List<ulong> tags) { Console.WriteLine("{0}/{1}: Adding tags {2}", id1, id2, string.Join(", ", tags.Select(x => string.Format("{0:X}", x)).ToArray())); activeTags.AddTags(id1, id2, tags); valueUpdater.UpdateValue(activeTags.GetActiveTags()); } // Log the removed tags on the command line and send the updated value to the OPC server. static void protocol_OnExit(OPCValueUpdater valueUpdater, ActiveTagStore activeTags, ushort id1, ushort id2, List<ulong> tags) { Console.WriteLine("{0}/{1}: Removing tags {2}", id1, id2, string.Join(", ", tags.Select(x => string.Format("{0:X}", x)).ToArray())); activeTags.RemoveTags(id1, id2, tags); valueUpdater.UpdateValue(activeTags.GetActiveTags()); } // State is invalid so clear store of what tags are active and wait for new dump.
static void protocol_OnSessionStart(OPCValueUpdater valueUpdater, ActiveTagStore activeTags) { Console.WriteLine("Session start"); valueUpdater.StartSessionDump(); activeTags.Clear(); } // Signal the end of the dump. static void protocol_OnKeepalive(OPCValueUpdater valueUpdater) { valueUpdater.EndSessionDump(); } #endregion
OPCReader Client Code
The OPCReader client subscribes to the OPC server tag and outputs its value to the console whenever it is updated. The following screenshot shows the output returned by the OPCReader Client.
The following example shows how this program uses the OPC Interface Server code to read an OPC item containing the tag IDs.

static void Main(string[] args) { // Parse OPC server parameters from App.config // // …configuration code here… // // Connect to OPC server try { Server = new OpcDaServer(OpcServerUrl, Credential); } catch (Opc.ConnectFailedException) { Console.WriteLine("Could not connect to OPC server"); return; } // Create subscription group and check existence of OPC tag try { Server.CreateSubscriptionGroup(TagName, PollRate, PollRate, DataChanged); Server.Read(TagName); } catch (IndexOutOfRangeException) { Console.WriteLine("OPC tag does not exist"); return; } while (true) System.Threading.Thread.Sleep(1000); } // Function to call whenever the value changes on the OPC server. // Outputs the current value to the console.
static void DataChanged(object data) { IEnumerable enumerable = data as IEnumerable; if (enumerable == null) { Console.WriteLine("Invalid type"); return; } Console.WriteLine("Current matching tags:"); bool empty = true; foreach (var tag in enumerable) { empty = false; Console.WriteLine(" {0:X}", tag); } if (empty) { Console.WriteLine(" None"); } Console.WriteLine(); }
|