#region Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved //----------------------------------------------------------------------------- // Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved // Web: https://www.technosoftware.com // // The source code in this file is covered under a dual-license scenario: // - Owner of a purchased license: SCLA 1.0 // - GPL V3: everybody else // // SCLA license terms accompanied with this source code. // See SCLA 1.0: https://technosoftware.com/license/Source_Code_License_Agreement.pdf // // GNU General Public License as published by the Free Software Foundation; // version 3 of the License are accompanied with this source code. // See https://technosoftware.com/license/GPLv3License.txt // // This source code is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. //----------------------------------------------------------------------------- #endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved #region Using Directives using System; using System.Collections.Generic; using System.Runtime.Serialization; #endregion namespace Technosoftware.DaAeHdaClient.Da { /// /// This class is the main interface to access an OPC Data Access server. /// [Serializable] public class TsCDaServer : OpcServer, ITsDaServer { #region Names Class /// A set of names for fields used in serialization. private class Names { internal const string Filters = "Filters"; internal const string Subscriptions = "Subscription"; } #endregion #region Fields /// /// A list of subscriptions for the server. /// private TsCDaSubscriptionCollection subscriptions_ = new TsCDaSubscriptionCollection(); /// /// The local copy of the result filters. /// private int filters_ = (int)TsCDaResultFilter.All | (int)TsCDaResultFilter.ClientHandle; #endregion #region Constructors, Destructor, Initialization /// /// Initializes the object. /// public TsCDaServer() { } /// /// Initializes the object with a factory and a default OpcUrl. /// /// The OpcFactory used to connect to remote servers. /// The network address of a remote server. public TsCDaServer(OpcFactory factory, OpcUrl url) : base(factory, url) { } /// /// Constructs a server by de-serializing its OpcUrl from the stream. /// protected TsCDaServer(SerializationInfo info, StreamingContext context) : base(info, context) { filters_ = (int)info.GetValue(Names.Filters, typeof(int)); var subscriptions = (TsCDaSubscription[])info.GetValue(Names.Subscriptions, typeof(TsCDaSubscription[])); if (subscriptions != null) { Array.ForEach(subscriptions, subscription => subscriptions_.Add(subscription)); } } #endregion #region Properties /// /// Returns an array of all subscriptions for the server. /// public TsCDaSubscriptionCollection Subscriptions => subscriptions_; /// /// The current result filters applied by the server. /// public int Filters => filters_; #endregion #region Class properties serialization helpers /// Serializes a server into a stream. public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue(Names.Filters, filters_); TsCDaSubscription[] subscriptions = null; if (subscriptions_.Count > 0) { subscriptions = new TsCDaSubscription[subscriptions_.Count]; for (var ii = 0; ii < subscriptions.Length; ii++) { subscriptions[ii] = subscriptions_[ii]; } } info.AddValue(Names.Subscriptions, subscriptions); } #endregion #region Public Methods /// Returns an unconnected copy of the server with the same OpcUrl. public override object Clone() { // clone the base object. var clone = (TsCDaServer)base.Clone(); // clone subscriptions. if (clone.subscriptions_ != null) { var subscriptions = new TsCDaSubscriptionCollection(); foreach (TsCDaSubscription subscription in clone.subscriptions_) { subscriptions.Add(subscription.Clone()); } clone.subscriptions_ = subscriptions; } // return clone. return clone; } /// Connects to the server with the specified OpcUrl and credentials. /// If an OPC specific error occur this exception is raised. The Result field includes then the OPC specific code. /// The network address of the remote server. /// Any protocol configuration or user authentication information. public override void Connect(OpcUrl url, OpcConnectData connectData) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); // connect to server. base.Connect(url, connectData); // all done if no subscriptions. if (subscriptions_ == null) { return; } // create subscriptions (should only happen if server has been deserialized). var subscriptions = new TsCDaSubscriptionCollection(); foreach (TsCDaSubscription template in subscriptions_) { // create subscription for template. try { subscriptions.Add(EstablishSubscription(template)); } catch { // Ignore exceptions here } } // save new set of subscriptions. subscriptions_ = subscriptions; } /// Disconnects from the server and releases all network resources. public override void Disconnect() { if (Server == null) throw new NotConnectedException(); // dispose of all subscriptions first. if (subscriptions_ != null) { foreach (TsCDaSubscription subscription in subscriptions_) { subscription.Dispose(); } subscriptions_ = null; } // disconnect from server. base.Disconnect(); } /// Returns the filters applied by the server to any item results returned to the client. /// A bit mask indicating which fields should be returned in any item results. public int GetResultFilters() { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); // update local cache. filters_ = ((ITsDaServer)Server).GetResultFilters(); // return filters. return filters_; } /// Sets the filters applied by the server to any item results returned to the client. /// A bit mask indicating which fields should be returned in any item results. public void SetResultFilters(int filters) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); // set filters on server. ((ITsDaServer)Server).SetResultFilters(filters); // cache updated filters. filters_ = filters; } /// Returns the current server status. /// The current server status. public OpcServerStatus GetServerStatus() { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); var status = ((ITsDaServer)Server).GetServerStatus(); if (status != null) { if (status.StatusInfo == null) { status.StatusInfo = GetString($"serverState.{status.ServerState}"); } } else { throw new NotConnectedException(); } return status; } /// Reads the current values for a set of items. /// The results of the read operation for each item. /// OPC XML-DA Server or OPC Data Access Server V3.x /// The set of items to read. public TsCDaItemValueResult[] Read(TsCDaItem[] items) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); return ((ITsDaServer)Server).Read(items); } /// Writes the value, quality and timestamp for a set of items. /// The results of the write operation for each item. /// OPC XML-DA Server or OPC Data Access Server V3.x /// The set of item values to write. public OpcItemResult[] Write(TsCDaItemValue[] items) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); return ((ITsDaServer)Server).Write(items); } /// /// Creates a new subscription. /// /// The new subscription object. /// OPC XML-DA Server or OPC Data Access Server V2.x / V3.x /// The initial state of the subscription. public virtual ITsCDaSubscription CreateSubscription(TsCDaSubscriptionState state) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (state == null) throw new ArgumentNullException(nameof(state)); if (Server == null) throw new NotConnectedException(); // create subscription on server. var subscription = ((ITsDaServer)Server).CreateSubscription(state); // set filters. subscription.SetResultFilters(filters_); // append new subscription to existing list. var subscriptions = new TsCDaSubscriptionCollection(); if (subscriptions_ != null) { foreach (TsCDaSubscription value in subscriptions_) { subscriptions.Add(value); } } subscriptions.Add(CreateSubscription(subscription)); // save new subscription list. subscriptions_ = subscriptions; // return new subscription. return subscriptions_[subscriptions_.Count - 1]; } /// /// Creates a new instance of the appropriate subscription object. /// /// The remote subscription object. protected virtual TsCDaSubscription CreateSubscription(ITsCDaSubscription subscription) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); return new TsCDaSubscription(this, subscription); } /// Cancels a subscription and releases all resources allocated for it. /// OPC XML-DA Server or OPC Data Access Server V2.x / V3.x /// The subscription to cancel. public virtual void CancelSubscription(ITsCDaSubscription subscription) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (subscription == null) throw new ArgumentNullException(nameof(subscription)); if (Server == null) throw new NotConnectedException(); // validate argument. if (!typeof(TsCDaSubscription).IsInstanceOfType(subscription)) { throw new ArgumentException(@"Incorrect object type.", nameof(subscription)); } if (!Equals(((TsCDaSubscription)subscription).Server)) { throw new ArgumentException(@"Server subscription.", nameof(subscription)); } // search for subscription in list of subscriptions. var subscriptions = new TsCDaSubscriptionCollection(); foreach (TsCDaSubscription current in subscriptions_) { if (!subscription.Equals(current)) { subscriptions.Add(current); } } // check if subscription was not found. if (subscriptions.Count == subscriptions_.Count) { throw new ArgumentException(@"Subscription not found.", nameof(subscription)); } // remove subscription from list of subscriptions. subscriptions_ = subscriptions; // cancel subscription on server. ((ITsDaServer)Server).CancelSubscription(((TsCDaSubscription)subscription).Subscription); } /// Fetches all the children of the root branch that meet the filter criteria. /// The set of elements found. /// OPC Data Access Server V2.x / V3.x /// The filters to use to limit the set of child elements returned. private TsCDaBrowseElement[] Browse( TsCDaBrowseFilters filters) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); TsCDaBrowsePosition position; var elementsList = new List(); var elements = ((ITsDaServer)Server).Browse(null, filters, out position); if (elements != null) { Browse(elements, filters, ref elementsList); } return elementsList.ToArray(); } private void Browse(TsCDaBrowseElement[] elements, TsCDaBrowseFilters filters, ref List elementsList) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); TsCDaBrowsePosition position; foreach (var element in elements) { if (element.HasChildren) { var itemId = new OpcItem(element.ItemPath, element.ItemName); var childElements = ((ITsDaServer)Server).Browse(itemId, filters, out position); if (childElements != null) { Browse(childElements, filters, ref elementsList); } } else { elementsList.Add(element); } } } /// Fetches the children of a branch that meet the filter criteria. /// The set of elements found. /// OPC XML-DA Server or OPC Data Access Server V2.x / V3.x /// The identifier of branch which is the target of the search. /// The filters to use to limit the set of child elements returned. /// An object used to continue a browse that could not be completed. public TsCDaBrowseElement[] Browse( OpcItem itemId, TsCDaBrowseFilters filters, out TsCDaBrowsePosition position) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); return ((ITsDaServer)Server).Browse(itemId, filters, out position); } /// Continues a browse operation with previously specified search criteria. /// The set of elements found. /// OPC XML-DA Server or OPC Data Access Server V2.x / V3.x /// An object containing the browse operation state information. public TsCDaBrowseElement[] BrowseNext(ref TsCDaBrowsePosition position) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); return ((ITsDaServer)Server).BrowseNext(ref position); } /// Returns the item properties for a set of items. /// A list of item identifiers. /// A list of properties to fetch for each item. /// Whether the property values should be returned with the properties. /// A list of properties for each item. public TsCDaItemPropertyCollection[] GetProperties( OpcItem[] itemIds, TsDaPropertyID[] propertyIDs, bool returnValues) { LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); if (Server == null) throw new NotConnectedException(); return ((ITsDaServer)Server).GetProperties(itemIds, propertyIDs, returnValues); } #endregion #region Private Methods /// /// Establishes a subscription based on the template provided. /// private TsCDaSubscription EstablishSubscription(TsCDaSubscription template) { // create subscription. var subscription = new TsCDaSubscription(this, ((ITsDaServer)Server).CreateSubscription(template.State)); // set filters. subscription.SetResultFilters(template.Filters); // add items. try { subscription.AddItems(template.Items); } catch { subscription.Dispose(); subscription = null; } // return new subscription. return subscription; } #endregion } }