From db591e0367c79fefb8ffd1ad32690fcc04c30fbf Mon Sep 17 00:00:00 2001 From: luosheng Date: Wed, 12 Jul 2023 06:42:28 +0800 Subject: [PATCH] Fix --- .../Modbus.Net.Opc/Modbus.Net.Opc.csproj | 2 +- Modbus.Net/Modbus.Net.Opc/OpcConnector.cs | 17 +- Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs | 9 +- Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs | 9 +- Modbus.Net/Modbus.Net.sln | 85 + .../Modbus.Net/Connector/ComConnector.cs | 14 +- .../DaAeHdaClient.Com/Ae/Interop.cs | 876 +++++ Technosoftware/DaAeHdaClient.Com/Ae/Result.cs | 51 + Technosoftware/DaAeHdaClient.Com/Ae/Server.cs | 1575 ++++++++ .../DaAeHdaClient.Com/Ae/Subscription.cs | 610 +++ .../DaAeHdaClient.Com/ApplicationInstance.cs | 97 + Technosoftware/DaAeHdaClient.Com/ComUtils.cs | 2792 +++++++++++++ .../DaAeHdaClient.Com/ConnectionPoint.cs | 107 + .../DaAeHdaClient.Com/Da/BrowsePosition.cs | 60 + .../DaAeHdaClient.Com/Da/Interop.cs | 906 +++++ Technosoftware/DaAeHdaClient.Com/Da/Result.cs | 128 + Technosoftware/DaAeHdaClient.Com/Da/Server.cs | 984 +++++ .../DaAeHdaClient.Com/Da/Subscription.cs | 2942 ++++++++++++++ .../DaAeHdaClient.Com/Da20/Server.cs | 1649 ++++++++ .../DaAeHdaClient.Com/Da20/Subscription.cs | 586 +++ .../DaAeHdaClient.Com/EnumString.cs | 128 + Technosoftware/DaAeHdaClient.Com/FILETIME.cs | 44 + Technosoftware/DaAeHdaClient.Com/Factory.cs | 290 ++ .../DaAeHdaClient.Com/Hda/Browser.cs | 441 +++ .../DaAeHdaClient.Com/Hda/DataCallback.cs | 591 +++ .../DaAeHdaClient.Com/Hda/Interop.cs | 441 +++ .../DaAeHdaClient.Com/Hda/Request.cs | 404 ++ .../DaAeHdaClient.Com/Hda/Result.cs | 68 + .../DaAeHdaClient.Com/Hda/Server.cs | 3473 +++++++++++++++++ Technosoftware/DaAeHdaClient.Com/Interop.cs | 1515 +++++++ .../DaAeHdaClient.Com/OpcDiscovery.cs | 204 + .../DaAeHdaClient.Com/SafeNativeMethods.cs | 1883 +++++++++ Technosoftware/DaAeHdaClient.Com/Server.cs | 599 +++ .../DaAeHdaClient.Com/ServerEnumerator.cs | 320 ++ .../Technosoftware.DaAeHdaClient.Com.csproj | 21 + .../Utilities/DCOMCallWatchdog.cs | 390 ++ .../DaAeHdaClient.Com/Utilities/Interop.cs | 1969 ++++++++++ Technosoftware/DaAeHdaClient/Ae/Attribute.cs | 72 + .../DaAeHdaClient/Ae/AttributeCollection.cs | 62 + .../DaAeHdaClient/Ae/AttributeDictionary.cs | 91 + .../DaAeHdaClient/Ae/AttributeValue.cs | 79 + .../DaAeHdaClient/Ae/BrowseElement.cs | 70 + .../DaAeHdaClient/Ae/BrowsePosition.cs | 129 + Technosoftware/DaAeHdaClient/Ae/BrowseType.cs | 44 + Technosoftware/DaAeHdaClient/Ae/Category.cs | 63 + Technosoftware/DaAeHdaClient/Ae/ChangeMask.cs | 75 + Technosoftware/DaAeHdaClient/Ae/Condition.cs | 217 + .../DaAeHdaClient/Ae/ConditionState.cs | 50 + .../DaAeHdaClient/Ae/EnabledStateResult.cs | 95 + .../DaAeHdaClient/Ae/EventAcknowledgement.cs | 93 + .../DaAeHdaClient/Ae/EventNotification.cs | 275 ++ Technosoftware/DaAeHdaClient/Ae/EventType.cs | 54 + Technosoftware/DaAeHdaClient/Ae/FilterType.cs | 65 + Technosoftware/DaAeHdaClient/Ae/IServer.cs | 202 + .../DaAeHdaClient/Ae/ISubscription.cs | 109 + Technosoftware/DaAeHdaClient/Ae/ItemUrl.cs | 93 + .../DaAeHdaClient/Ae/ItemUrlCollection.cs | 60 + Technosoftware/DaAeHdaClient/Ae/Server.cs | 661 ++++ .../DaAeHdaClient/Ae/ServerState.cs | 68 + Technosoftware/DaAeHdaClient/Ae/StateMask.cs | 70 + .../DaAeHdaClient/Ae/SubCondition.cs | 83 + .../DaAeHdaClient/Ae/Subscription.cs | 610 +++ .../DaAeHdaClient/Ae/SubscriptionFilters.cs | 232 ++ .../DaAeHdaClient/Ae/SubscriptionState.cs | 88 + .../DaAeHdaClient/ApplicationInstance.cs | 66 + .../DaAeHdaClient/Cpx/ComplexItem.cs | 715 ++++ .../DaAeHdaClient/Cpx/ComplexTypeCache.cs | 207 + .../DaAeHdaClient/Cpx/ComplexValue.cs | 49 + Technosoftware/DaAeHdaClient/Cpx/Context.cs | 71 + .../Cpx/InvalidDataInBufferException.cs | 48 + .../Cpx/InvalidDataToWriteException.cs | 48 + .../Cpx/InvalidSchemaException.cs | 48 + Technosoftware/DaAeHdaClient/Cpx/OPCBinary.cs | 269 ++ .../DaAeHdaClient/Cpx/OPCBinary.xsd | 165 + .../DaAeHdaClient/Da/AccessRight.cs | 45 + Technosoftware/DaAeHdaClient/Da/Browse.cs | 93 + .../DaAeHdaClient/Da/BrowseElement.cs | 87 + .../DaAeHdaClient/Da/BrowseFilter.cs | 48 + .../DaAeHdaClient/Da/BrowseFilters.cs | 97 + Technosoftware/DaAeHdaClient/Da/EuType.cs | 45 + Technosoftware/DaAeHdaClient/Da/IServer.cs | 109 + .../DaAeHdaClient/Da/ISubscription.cs | 226 ++ Technosoftware/DaAeHdaClient/Da/Item.cs | 150 + .../DaAeHdaClient/Da/ItemCollection.cs | 313 ++ .../DaAeHdaClient/Da/ItemProperty.cs | 100 + .../Da/ItemPropertyCollection.cs | 175 + Technosoftware/DaAeHdaClient/Da/ItemResult.cs | 104 + Technosoftware/DaAeHdaClient/Da/ItemValue.cs | 137 + .../DaAeHdaClient/Da/ItemValueResult.cs | 123 + Technosoftware/DaAeHdaClient/Da/LimitBits.cs | 44 + Technosoftware/DaAeHdaClient/Da/Property.cs | 328 ++ .../DaAeHdaClient/Da/PropertyDescription.cs | 200 + Technosoftware/DaAeHdaClient/Da/PropertyID.cs | 198 + Technosoftware/DaAeHdaClient/Da/Quality.cs | 251 ++ .../DaAeHdaClient/Da/QualityBits.cs | 114 + .../DaAeHdaClient/Da/QualityMasks.cs | 40 + Technosoftware/DaAeHdaClient/Da/Request.cs | 70 + .../DaAeHdaClient/Da/ResultFilter.cs | 75 + Technosoftware/DaAeHdaClient/Da/Server.cs | 500 +++ .../DaAeHdaClient/Da/ServerState.cs | 68 + Technosoftware/DaAeHdaClient/Da/StateMask.cs | 92 + .../DaAeHdaClient/Da/Subscription.cs | 571 +++ .../Da/SubscriptionCollection.cs | 316 ++ .../DaAeHdaClient/Da/SubscriptionState.cs | 172 + Technosoftware/DaAeHdaClient/Hda/Aggregate.cs | 70 + .../DaAeHdaClient/Hda/AggregateCollection.cs | 178 + .../DaAeHdaClient/Hda/AggregateId.cs | 141 + .../DaAeHdaClient/Hda/AnnotationValue.cs | 84 + .../Hda/AnnotationValueCollection.cs | 348 ++ Technosoftware/DaAeHdaClient/Hda/Attribute.cs | 75 + .../DaAeHdaClient/Hda/AttributeCollection.cs | 174 + .../DaAeHdaClient/Hda/AttributeID.cs | 72 + .../DaAeHdaClient/Hda/AttributeValue.cs | 69 + .../Hda/AttributeValueCollection.cs | 323 ++ .../DaAeHdaClient/Hda/BrowseElement.cs | 75 + .../DaAeHdaClient/Hda/BrowseFilter.cs | 72 + .../Hda/BrowseFilterCollection.cs | 178 + .../DaAeHdaClient/Hda/BrowsePosition.cs | 52 + Technosoftware/DaAeHdaClient/Hda/EditType.cs | 53 + .../DaAeHdaClient/Hda/IActualTime.cs | 44 + Technosoftware/DaAeHdaClient/Hda/IBrowser.cs | 63 + Technosoftware/DaAeHdaClient/Hda/IServer.cs | 546 +++ Technosoftware/DaAeHdaClient/Hda/Item.cs | 74 + .../Hda/ItemAttributeCollection.cs | 345 ++ .../DaAeHdaClient/Hda/ItemCollection.cs | 315 ++ .../DaAeHdaClient/Hda/ItemResult.cs | 85 + .../DaAeHdaClient/Hda/ItemTimeCollection.cs | 299 ++ Technosoftware/DaAeHdaClient/Hda/ItemValue.cs | 93 + .../DaAeHdaClient/Hda/ItemValueCollection.cs | 369 ++ .../DaAeHdaClient/Hda/ItemValueResult.cs | 90 + .../DaAeHdaClient/Hda/ModifiedValue.cs | 67 + .../Hda/ModifiedValueCollection.cs | 68 + Technosoftware/DaAeHdaClient/Hda/Operator.cs | 64 + Technosoftware/DaAeHdaClient/Hda/Quality.cs | 80 + .../DaAeHdaClient/Hda/RelativeTime.cs | 74 + .../DaAeHdaClient/Hda/ResultCollection.cs | 299 ++ Technosoftware/DaAeHdaClient/Hda/Server.cs | 1072 +++++ .../DaAeHdaClient/Hda/ServerState.cs | 47 + Technosoftware/DaAeHdaClient/Hda/Time.cs | 303 ++ .../DaAeHdaClient/Hda/TimeOffset.cs | 70 + .../DaAeHdaClient/Hda/TimeOffsetCollection.cs | 265 ++ Technosoftware/DaAeHdaClient/Hda/Trend.cs | 1042 +++++ .../DaAeHdaClient/Hda/TrendCollection.cs | 309 ++ .../Interfaces/IOpcBrowsePosition.cs | 35 + .../DaAeHdaClient/Interfaces/IOpcDiscovery.cs | 57 + .../DaAeHdaClient/Interfaces/IOpcFactory.cs | 44 + .../DaAeHdaClient/Interfaces/IOpcRequest.cs | 39 + .../DaAeHdaClient/Interfaces/IOpcResult.cs | 44 + .../DaAeHdaClient/Interfaces/IOpcServer.cs | 94 + .../DaAeHdaClient/LicenseHandler.cs | 394 ++ .../DaAeHdaClient/OpcConnectData.cs | 191 + Technosoftware/DaAeHdaClient/OpcConvert.cs | 544 +++ Technosoftware/DaAeHdaClient/OpcFactory.cs | 124 + Technosoftware/DaAeHdaClient/OpcItem.cs | 114 + .../DaAeHdaClient/OpcItemCollection.cs | 157 + Technosoftware/DaAeHdaClient/OpcItemResult.cs | 126 + .../DaAeHdaClient/OpcItemResultCollection.cs | 156 + Technosoftware/DaAeHdaClient/OpcNamespace.cs | 55 + .../DaAeHdaClient/OpcReadOnlyCollection.cs | 191 + .../DaAeHdaClient/OpcReadOnlyDictionary.cs | 263 ++ Technosoftware/DaAeHdaClient/OpcResult.cs | 811 ++++ .../DaAeHdaClient/OpcResultException.cs | 54 + Technosoftware/DaAeHdaClient/OpcServer.cs | 780 ++++ .../DaAeHdaClient/OpcServerDescription.cs | 85 + .../DaAeHdaClient/OpcServerDetail.cs | 55 + .../DaAeHdaClient/OpcServerState.cs | 84 + .../DaAeHdaClient/OpcServerStatus.cs | 173 + .../DaAeHdaClient/OpcSpecification.cs | 137 + Technosoftware/DaAeHdaClient/OpcType.cs | 114 + Technosoftware/DaAeHdaClient/OpcUrl.cs | 318 ++ Technosoftware/DaAeHdaClient/OpcUrlScheme.cs | 60 + .../DaAeHdaClient/OpcUserIdentity.cs | 403 ++ .../DaAeHdaClient/OpcWriteableCollection.cs | 361 ++ .../DaAeHdaClient/OpcWriteableDictionary.cs | 375 ++ .../Properties/Resources.Designer.cs | 63 + .../DaAeHdaClient/Properties/Resources.resx | 120 + .../DaAeHdaClient/Resources/Strings.resx | 144 + .../Technosoftware.DaAeHdaClient.csproj | 15 + .../DaAeHdaClient/Utilities/ConfigUtils.cs | 87 + .../DaAeHdaClient/Utilities/HiResClock.cs | 113 + .../DaAeHdaClient/Utilities/Utils.cs | 731 ++++ Technosoftware/OpcRcw/Ae/AlarmsAndEvents.cs | 800 ++++ Technosoftware/OpcRcw/Comn/Common.cs | 361 ++ Technosoftware/OpcRcw/Da/DataAccess.cs | 1417 +++++++ .../OpcRcw/Hda/HistoricalDataAccess.cs | 1014 +++++ Technosoftware/OpcRcw/Security/Security.cs | 56 + .../OpcRcw/Technosoftware.OpcRcw.csproj | 17 + h-opc/h-opc/h-opc.csproj | 7 +- 188 files changed, 56088 insertions(+), 9 deletions(-) create mode 100644 Technosoftware/DaAeHdaClient.Com/Ae/Interop.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Ae/Result.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Ae/Server.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Ae/Subscription.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/ApplicationInstance.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/ComUtils.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/ConnectionPoint.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Da/BrowsePosition.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Da/Interop.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Da/Result.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Da/Server.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Da/Subscription.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Da20/Server.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Da20/Subscription.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/EnumString.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/FILETIME.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Factory.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Hda/Browser.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Hda/DataCallback.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Hda/Interop.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Hda/Request.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Hda/Result.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Hda/Server.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Interop.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/OpcDiscovery.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/SafeNativeMethods.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Server.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/ServerEnumerator.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Technosoftware.DaAeHdaClient.Com.csproj create mode 100644 Technosoftware/DaAeHdaClient.Com/Utilities/DCOMCallWatchdog.cs create mode 100644 Technosoftware/DaAeHdaClient.Com/Utilities/Interop.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/Attribute.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/AttributeCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/AttributeDictionary.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/AttributeValue.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/BrowseElement.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/BrowsePosition.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/BrowseType.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/Category.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/ChangeMask.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/Condition.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/ConditionState.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/EnabledStateResult.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/EventAcknowledgement.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/EventNotification.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/EventType.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/FilterType.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/IServer.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/ISubscription.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/ItemUrl.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/ItemUrlCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/Server.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/ServerState.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/StateMask.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/SubCondition.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/Subscription.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/SubscriptionFilters.cs create mode 100644 Technosoftware/DaAeHdaClient/Ae/SubscriptionState.cs create mode 100644 Technosoftware/DaAeHdaClient/ApplicationInstance.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/ComplexItem.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/ComplexTypeCache.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/ComplexValue.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/Context.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/InvalidDataInBufferException.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/InvalidDataToWriteException.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/InvalidSchemaException.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/OPCBinary.cs create mode 100644 Technosoftware/DaAeHdaClient/Cpx/OPCBinary.xsd create mode 100644 Technosoftware/DaAeHdaClient/Da/AccessRight.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/Browse.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/BrowseElement.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/BrowseFilter.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/BrowseFilters.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/EuType.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/IServer.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ISubscription.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/Item.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ItemCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ItemProperty.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ItemPropertyCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ItemResult.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ItemValue.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ItemValueResult.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/LimitBits.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/Property.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/PropertyDescription.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/PropertyID.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/Quality.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/QualityBits.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/QualityMasks.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/Request.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ResultFilter.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/Server.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/ServerState.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/StateMask.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/Subscription.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/SubscriptionCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Da/SubscriptionState.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Aggregate.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AggregateCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AggregateId.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AnnotationValue.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AnnotationValueCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Attribute.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AttributeCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AttributeID.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AttributeValue.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/AttributeValueCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/BrowseElement.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/BrowseFilter.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/BrowseFilterCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/BrowsePosition.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/EditType.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/IActualTime.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/IBrowser.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/IServer.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Item.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ItemAttributeCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ItemCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ItemResult.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ItemTimeCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ItemValue.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ItemValueCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ItemValueResult.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ModifiedValue.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ModifiedValueCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Operator.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Quality.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/RelativeTime.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ResultCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Server.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/ServerState.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Time.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/TimeOffset.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/TimeOffsetCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/Trend.cs create mode 100644 Technosoftware/DaAeHdaClient/Hda/TrendCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/Interfaces/IOpcBrowsePosition.cs create mode 100644 Technosoftware/DaAeHdaClient/Interfaces/IOpcDiscovery.cs create mode 100644 Technosoftware/DaAeHdaClient/Interfaces/IOpcFactory.cs create mode 100644 Technosoftware/DaAeHdaClient/Interfaces/IOpcRequest.cs create mode 100644 Technosoftware/DaAeHdaClient/Interfaces/IOpcResult.cs create mode 100644 Technosoftware/DaAeHdaClient/Interfaces/IOpcServer.cs create mode 100644 Technosoftware/DaAeHdaClient/LicenseHandler.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcConnectData.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcConvert.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcFactory.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcItem.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcItemCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcItemResult.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcItemResultCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcNamespace.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcReadOnlyCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcReadOnlyDictionary.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcResult.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcResultException.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcServer.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcServerDescription.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcServerDetail.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcServerState.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcServerStatus.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcSpecification.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcType.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcUrl.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcUrlScheme.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcUserIdentity.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcWriteableCollection.cs create mode 100644 Technosoftware/DaAeHdaClient/OpcWriteableDictionary.cs create mode 100644 Technosoftware/DaAeHdaClient/Properties/Resources.Designer.cs create mode 100644 Technosoftware/DaAeHdaClient/Properties/Resources.resx create mode 100644 Technosoftware/DaAeHdaClient/Resources/Strings.resx create mode 100644 Technosoftware/DaAeHdaClient/Technosoftware.DaAeHdaClient.csproj create mode 100644 Technosoftware/DaAeHdaClient/Utilities/ConfigUtils.cs create mode 100644 Technosoftware/DaAeHdaClient/Utilities/HiResClock.cs create mode 100644 Technosoftware/DaAeHdaClient/Utilities/Utils.cs create mode 100644 Technosoftware/OpcRcw/Ae/AlarmsAndEvents.cs create mode 100644 Technosoftware/OpcRcw/Comn/Common.cs create mode 100644 Technosoftware/OpcRcw/Da/DataAccess.cs create mode 100644 Technosoftware/OpcRcw/Hda/HistoricalDataAccess.cs create mode 100644 Technosoftware/OpcRcw/Security/Security.cs create mode 100644 Technosoftware/OpcRcw/Technosoftware.OpcRcw.csproj diff --git a/Modbus.Net/Modbus.Net.Opc/Modbus.Net.Opc.csproj b/Modbus.Net/Modbus.Net.Opc/Modbus.Net.Opc.csproj index 6a765e5..f077582 100644 --- a/Modbus.Net/Modbus.Net.Opc/Modbus.Net.Opc.csproj +++ b/Modbus.Net/Modbus.Net.Opc/Modbus.Net.Opc.csproj @@ -20,7 +20,7 @@ True True True - MIT + GPL-3.0-only README.md snupkg AnyCPU diff --git a/Modbus.Net/Modbus.Net.Opc/OpcConnector.cs b/Modbus.Net/Modbus.Net.Opc/OpcConnector.cs index 0deff3f..380a733 100644 --- a/Modbus.Net/Modbus.Net.Opc/OpcConnector.cs +++ b/Modbus.Net/Modbus.Net.Opc/OpcConnector.cs @@ -101,14 +101,23 @@ namespace Modbus.Net.Opc { resultTrans = (byte)0; } - else if (result.Value.ToString() == "True") + else if (result.Value?.ToString() == "True") { resultTrans = (byte)1; } - else + else if (result.Value != null) { resultTrans = result.Value; } + else + { + logger.LogError($"Opc Machine {ConnectionToken} Read Opc tag {tag} for value null"); + return new OpcParamOut + { + Success = false, + Value = Encoding.ASCII.GetBytes("NoData") + }; + } logger.LogInformation($"Opc Machine {ConnectionToken} Read Opc tag {tag} for value {result.Value} {result.Value.GetType().FullName}"); return new OpcParamOut { @@ -116,11 +125,12 @@ namespace Modbus.Net.Opc Value = BigEndianLsbValueHelper.Instance.GetBytes(resultTrans, resultTrans.GetType()) }; } + logger.LogError($"Opc Machine {ConnectionToken} Read Opc tag null"); return new OpcParamOut { Success = false, Value = Encoding.ASCII.GetBytes("NoData") - }; + }; } else { @@ -156,6 +166,7 @@ namespace Modbus.Net.Opc catch (Exception e) { logger.LogError(e, "Opc client {ConnectionToken} read exception", ConnectionToken); + Disconnect(); return new OpcParamOut { Success = false, diff --git a/Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs b/Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs index c30600a..840a8c9 100644 --- a/Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs +++ b/Modbus.Net/Modbus.Net.Opc/OpcDaConnector.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Modbus.Net.Opc { @@ -19,7 +20,6 @@ namespace Modbus.Net.Opc /// Opc DA 服务地址 protected OpcDaConnector(string host) : base(host) { - Client = new MyDaClient(new Uri(ConnectionToken)); } /// @@ -36,5 +36,12 @@ namespace Modbus.Net.Opc } return _instances[host]; } + + /// + public override Task ConnectAsync() + { + if (Client == null) Client = new MyDaClient(new Uri(ConnectionToken)); + return base.ConnectAsync(); + } } } \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs b/Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs index 228c495..cd59c72 100644 --- a/Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs +++ b/Modbus.Net/Modbus.Net.Opc/OpcUaConnector.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Modbus.Net.Opc { @@ -19,7 +20,6 @@ namespace Modbus.Net.Opc /// Opc UA 服务地址 protected OpcUaConnector(string host) : base(host) { - Client = new MyUaClient(new Uri(ConnectionToken)); } /// @@ -36,5 +36,12 @@ namespace Modbus.Net.Opc } return _instances[host]; } + + /// + public override Task ConnectAsync() + { + if (Client == null) Client = new MyUaClient(new Uri(ConnectionToken)); + return base.ConnectAsync(); + } } } \ No newline at end of file diff --git a/Modbus.Net/Modbus.Net.sln b/Modbus.Net/Modbus.Net.sln index 2e32314..92c3165 100644 --- a/Modbus.Net/Modbus.Net.sln +++ b/Modbus.Net/Modbus.Net.sln @@ -7,6 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net", "Modbus.Net\Mo EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64472271-8B95-4036-ACF9-C10941C1DB0A}" ProjectSection(SolutionItems) = preProject + ..\LICENSE = ..\LICENSE ..\README.md = ..\README.md EndProjectSection EndProject @@ -38,64 +39,148 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "h-opc", "..\h-opc\h-opc\h-o EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Modbus.Net.BigEndian3412", "Modbus.Net.BigEndian3412\Modbus.Net.BigEndian3412.csproj", "{D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.OpcRcw", "..\Technosoftware\OpcRcw\Technosoftware.OpcRcw.csproj", "{7689CBF8-1992-467D-AD45-E1464F705220}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.DaAeHdaClient", "..\Technosoftware\DaAeHdaClient\Technosoftware.DaAeHdaClient.csproj", "{116160B2-7D6D-40A2-839C-7997BC0E1A0C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Technosoftware.DaAeHdaClient.Com", "..\Technosoftware\DaAeHdaClient.Com\Technosoftware.DaAeHdaClient.Com.csproj", "{ACAF0A16-FC51-4369-BFA8-484FF20707D7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Debug|x64.Build.0 = Debug|Any CPU {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|Any CPU.ActiveCfg = Release|Any CPU {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|Any CPU.Build.0 = Release|Any CPU + {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|x64.ActiveCfg = Release|Any CPU + {124EBEF2-8960-4447-84CF-1D683B1EF7CC}.Release|x64.Build.0 = Release|Any CPU {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Debug|x64.Build.0 = Debug|Any CPU {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|Any CPU.Build.0 = Release|Any CPU + {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|x64.ActiveCfg = Release|Any CPU + {FDCA72BA-6D06-4DE0-B873-C11C4AC853AD}.Release|x64.Build.0 = Release|Any CPU {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|x64.ActiveCfg = Debug|Any CPU + {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Debug|x64.Build.0 = Debug|Any CPU {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|Any CPU.ActiveCfg = Release|Any CPU {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|Any CPU.Build.0 = Release|Any CPU + {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|x64.ActiveCfg = Release|Any CPU + {6258F9D9-0DF4-497F-9F3B-6D2F6F752A21}.Release|x64.Build.0 = Release|Any CPU {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|x64.ActiveCfg = Debug|Any CPU + {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Debug|x64.Build.0 = Debug|Any CPU {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|Any CPU.ActiveCfg = Release|Any CPU {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|Any CPU.Build.0 = Release|Any CPU + {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|x64.ActiveCfg = Release|Any CPU + {3BB01E98-3D45-454A-A1BD-49D7B2C83B74}.Release|x64.Build.0 = Release|Any CPU {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|x64.ActiveCfg = Debug|Any CPU + {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Debug|x64.Build.0 = Debug|Any CPU {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|Any CPU.Build.0 = Release|Any CPU + {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|x64.ActiveCfg = Release|Any CPU + {22A35CA8-CDCF-416D-BA84-08C933B4A3DE}.Release|x64.Build.0 = Release|Any CPU {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|x64.ActiveCfg = Debug|Any CPU + {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Debug|x64.Build.0 = Debug|Any CPU {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|Any CPU.Build.0 = Release|Any CPU + {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|x64.ActiveCfg = Release|Any CPU + {D4AF0E1E-676E-43B6-BAA3-BFC329D68C80}.Release|x64.Build.0 = Release|Any CPU {1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|x64.ActiveCfg = Debug|Any CPU + {1857DA63-3335-428F-84D8-1FA4F8178643}.Debug|x64.Build.0 = Debug|Any CPU {1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.ActiveCfg = Release|Any CPU {1857DA63-3335-428F-84D8-1FA4F8178643}.Release|Any CPU.Build.0 = Release|Any CPU + {1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.ActiveCfg = Release|Any CPU + {1857DA63-3335-428F-84D8-1FA4F8178643}.Release|x64.Build.0 = Release|Any CPU {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|x64.ActiveCfg = Debug|Any CPU + {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Debug|x64.Build.0 = Debug|Any CPU {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|Any CPU.Build.0 = Release|Any CPU + {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|x64.ActiveCfg = Release|Any CPU + {C854A379-C5EA-4CAC-9C5F-7291372D1D3F}.Release|x64.Build.0 = Release|Any CPU {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Debug|x64.Build.0 = Debug|Any CPU {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|Any CPU.Build.0 = Release|Any CPU + {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|x64.ActiveCfg = Release|Any CPU + {AA3A42D2-0502-41D3-929A-BAB729DF07D6}.Release|x64.Build.0 = Release|Any CPU {414956B8-DBD4-414C-ABD3-565580739646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {414956B8-DBD4-414C-ABD3-565580739646}.Debug|Any CPU.Build.0 = Debug|Any CPU + {414956B8-DBD4-414C-ABD3-565580739646}.Debug|x64.ActiveCfg = Debug|Any CPU + {414956B8-DBD4-414C-ABD3-565580739646}.Debug|x64.Build.0 = Debug|Any CPU {414956B8-DBD4-414C-ABD3-565580739646}.Release|Any CPU.ActiveCfg = Release|Any CPU {414956B8-DBD4-414C-ABD3-565580739646}.Release|Any CPU.Build.0 = Release|Any CPU + {414956B8-DBD4-414C-ABD3-565580739646}.Release|x64.ActiveCfg = Release|Any CPU + {414956B8-DBD4-414C-ABD3-565580739646}.Release|x64.Build.0 = Release|Any CPU {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Debug|x64.Build.0 = Debug|Any CPU {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.ActiveCfg = Release|Any CPU {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|Any CPU.Build.0 = Release|Any CPU + {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.ActiveCfg = Release|Any CPU + {C4FA55AF-80ED-4467-948F-8EF865C8A5A5}.Release|x64.Build.0 = Release|Any CPU {DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC6425E4-1409-488D-A014-4DCC909CF542}.Debug|x64.Build.0 = Debug|Any CPU {DC6425E4-1409-488D-A014-4DCC909CF542}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC6425E4-1409-488D-A014-4DCC909CF542}.Release|Any CPU.Build.0 = Release|Any CPU + {DC6425E4-1409-488D-A014-4DCC909CF542}.Release|x64.ActiveCfg = Release|Any CPU + {DC6425E4-1409-488D-A014-4DCC909CF542}.Release|x64.Build.0 = Release|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|x64.ActiveCfg = Debug|Any CPU + {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Debug|x64.Build.0 = Debug|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|Any CPU.ActiveCfg = Release|Any CPU {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|Any CPU.Build.0 = Release|Any CPU + {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.ActiveCfg = Release|Any CPU + {D48D4F79-1DA2-4C91-A9EE-FDCAEC09E808}.Release|x64.Build.0 = Release|Any CPU + {7689CBF8-1992-467D-AD45-E1464F705220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7689CBF8-1992-467D-AD45-E1464F705220}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7689CBF8-1992-467D-AD45-E1464F705220}.Debug|x64.ActiveCfg = Debug|x64 + {7689CBF8-1992-467D-AD45-E1464F705220}.Debug|x64.Build.0 = Debug|x64 + {7689CBF8-1992-467D-AD45-E1464F705220}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7689CBF8-1992-467D-AD45-E1464F705220}.Release|Any CPU.Build.0 = Release|Any CPU + {7689CBF8-1992-467D-AD45-E1464F705220}.Release|x64.ActiveCfg = Release|x64 + {7689CBF8-1992-467D-AD45-E1464F705220}.Release|x64.Build.0 = Release|x64 + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|x64.ActiveCfg = Debug|Any CPU + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Debug|x64.Build.0 = Debug|Any CPU + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|Any CPU.Build.0 = Release|Any CPU + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|x64.ActiveCfg = Release|Any CPU + {116160B2-7D6D-40A2-839C-7997BC0E1A0C}.Release|x64.Build.0 = Release|Any CPU + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|x64.ActiveCfg = Debug|x64 + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Debug|x64.Build.0 = Debug|x64 + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|Any CPU.Build.0 = Release|Any CPU + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|x64.ActiveCfg = Release|x64 + {ACAF0A16-FC51-4369-BFA8-484FF20707D7}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Modbus.Net/Modbus.Net/Connector/ComConnector.cs b/Modbus.Net/Modbus.Net/Connector/ComConnector.cs index 0a1b1c6..43900ce 100644 --- a/Modbus.Net/Modbus.Net/Connector/ComConnector.cs +++ b/Modbus.Net/Modbus.Net/Connector/ComConnector.cs @@ -506,7 +506,7 @@ namespace Modbus.Net _taskCancel = true; } - private void ReceiveMessage() + private async Task ReceiveMessage() { while (!_taskCancel) { @@ -549,9 +549,19 @@ namespace Modbus.Net CacheBytes.RemoveRange(0, confirmed.Item1.Length); } } - else if (confirmed.Item2 == false) + else { + lock (CacheBytes) + { + CacheBytes.RemoveRange(0, confirmed.Item1.Length); + } + + var sendMessage = InvokeReturnMessage(confirmed.Item1); //主动传输事件 + if (sendMessage != null) + { + await SendMsgWithoutConfirm(sendMessage); + } } } } diff --git a/Technosoftware/DaAeHdaClient.Com/Ae/Interop.cs b/Technosoftware/DaAeHdaClient.Com/Ae/Interop.cs new file mode 100644 index 0000000..8814f07 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Ae/Interop.cs @@ -0,0 +1,876 @@ +#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.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Ae; +using Technosoftware.DaAeHdaClient.Da; +#endregion + +#pragma warning disable 0618 + +namespace Technosoftware.DaAeHdaClient.Com.Ae +{ + /// + /// Defines COM marshalling/unmarshalling functions for AE. + /// + internal class Interop + { + /// + /// Converts a standard FILETIME to an OpcRcw.Ae.FILETIME structure. + /// + internal static OpcRcw.Ae.FILETIME Convert(FILETIME input) + { + var output = new OpcRcw.Ae.FILETIME(); + output.dwLowDateTime = input.dwLowDateTime; + output.dwHighDateTime = input.dwHighDateTime; + return output; + } + + /// + /// Converts an OpcRcw.Ae.FILETIME to a standard FILETIME structure. + /// + internal static FILETIME Convert(OpcRcw.Ae.FILETIME input) + { + var output = new FILETIME(); + output.dwLowDateTime = input.dwLowDateTime; + output.dwHighDateTime = input.dwHighDateTime; + return output; + } + + /// + /// Converts the HRESULT to a system type. + /// + internal static OpcResult GetResultID(int input) + { + // must check for this error because of a code collision with a DA code. + if (input == Result.E_INVALIDBRANCHNAME) + { + return OpcResult.Ae.E_INVALIDBRANCHNAME; + } + + return Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input); + } + + /// + /// Unmarshals and deallocates a OPCEVENTSERVERSTATUS structure. + /// + internal static OpcServerStatus GetServerStatus(ref IntPtr pInput, bool deallocate) + { + OpcServerStatus output = null; + + if (pInput != IntPtr.Zero) + { + var status = (OpcRcw.Ae.OPCEVENTSERVERSTATUS)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Ae.OPCEVENTSERVERSTATUS)); + + output = new OpcServerStatus(); + + output.VendorInfo = status.szVendorInfo; + output.ProductVersion = string.Format("{0}.{1}.{2}", status.wMajorVersion, status.wMinorVersion, status.wBuildNumber); + output.MajorVersion = status.wMajorVersion; + output.MinorVersion = status.wMinorVersion; + output.BuildNumber = status.wBuildNumber; + + output.ServerState = (OpcServerState)status.dwServerState; + output.StatusInfo = null; + output.StartTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftStartTime)); + output.CurrentTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftCurrentTime)); + output.LastUpdateTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftLastUpdateTime)); + + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Ae.OPCEVENTSERVERSTATUS)); + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Converts a NodeType value to the OPCAEBROWSETYPE equivalent. + /// + internal static OpcRcw.Ae.OPCAEBROWSETYPE GetBrowseType(TsCAeBrowseType input) + { + switch (input) + { + case TsCAeBrowseType.Area: return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_AREA; + case TsCAeBrowseType.Source: return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_SOURCE; + } + + return OpcRcw.Ae.OPCAEBROWSETYPE.OPC_AREA; + } + + /// + /// Converts an array of ONEVENTSTRUCT structs to an array of EventNotification objects. + /// + internal static TsCAeEventNotification[] GetEventNotifications(OpcRcw.Ae.ONEVENTSTRUCT[] input) + { + TsCAeEventNotification[] output = null; + + if (input != null && input.Length > 0) + { + output = new TsCAeEventNotification[input.Length]; + + for (var ii = 0; ii < input.Length; ii++) + { + output[ii] = GetEventNotification(input[ii]); + } + } + + return output; + } + + /// + /// Converts a ONEVENTSTRUCT struct to a EventNotification object. + /// + internal static TsCAeEventNotification GetEventNotification(OpcRcw.Ae.ONEVENTSTRUCT input) + { + var output = new TsCAeEventNotification(); + + output.SourceID = input.szSource; + output.Time = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(input.ftTime)); + output.Severity = input.dwSeverity; + output.Message = input.szMessage; + output.EventType = (TsCAeEventType)input.dwEventType; + output.EventCategory = input.dwEventCategory; + output.ChangeMask = input.wChangeMask; + output.NewState = input.wNewState; + output.Quality = new TsCDaQuality(input.wQuality); + output.ConditionName = input.szConditionName; + output.SubConditionName = input.szSubconditionName; + output.AckRequired = input.bAckRequired != 0; + output.ActiveTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(input.ftActiveTime)); + output.Cookie = input.dwCookie; + output.ActorID = input.szActorID; + + var attributes = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref input.pEventAttributes, input.dwNumEventAttrs, false); + + output.SetAttributes(attributes); + + return output; + } + + /// + /// Converts an array of OPCCONDITIONSTATE structs to an array of Condition objects. + /// + internal static TsCAeCondition[] GetConditions(ref IntPtr pInput, int count, bool deallocate) + { + TsCAeCondition[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCAeCondition[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + var condition = (OpcRcw.Ae.OPCCONDITIONSTATE)Marshal.PtrToStructure(pos, typeof(OpcRcw.Ae.OPCCONDITIONSTATE)); + + output[ii] = new TsCAeCondition(); + + output[ii].State = condition.wState; + output[ii].Quality = new TsCDaQuality(condition.wQuality); + output[ii].Comment = condition.szComment; + output[ii].AcknowledgerID = condition.szAcknowledgerID; + output[ii].CondLastActive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftCondLastActive)); + output[ii].CondLastInactive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftCondLastInactive)); + output[ii].SubCondLastActive = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftSubCondLastActive)); + output[ii].LastAckTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(condition.ftLastAckTime)); + + output[ii].ActiveSubCondition.Name = condition.szActiveSubCondition; + output[ii].ActiveSubCondition.Definition = condition.szASCDefinition; + output[ii].ActiveSubCondition.Severity = condition.dwASCSeverity; + output[ii].ActiveSubCondition.Description = condition.szASCDescription; + + // unmarshal sub-conditions. + var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCNames, condition.dwNumSCs, deallocate); + var severities = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref condition.pdwSCSeverities, condition.dwNumSCs, deallocate); + var definitions = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCDefinitions, condition.dwNumSCs, deallocate); + var descriptions = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref condition.pszSCDescriptions, condition.dwNumSCs, deallocate); + + output[ii].SubConditions.Clear(); + + if (condition.dwNumSCs > 0) + { + for (var jj = 0; jj < names.Length; jj++) + { + var subcondition = new TsCAeSubCondition(); + + subcondition.Name = names[jj]; + subcondition.Severity = severities[jj]; + subcondition.Definition = definitions[jj]; + subcondition.Description = descriptions[jj]; + + output[ii].SubConditions.Add(subcondition); + } + } + + // unmarshal attributes. + var values = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref condition.pEventAttributes, condition.dwNumEventAttrs, deallocate); + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref condition.pErrors, condition.dwNumEventAttrs, deallocate); + + output[ii].Attributes.Clear(); + + if (condition.dwNumEventAttrs > 0) + { + for (var jj = 0; jj < values.Length; jj++) + { + var attribute = new TsCAeAttributeValue(); + + attribute.ID = 0; + attribute.Value = values[jj]; + attribute.Result = GetResultID(errors[jj]); + + output[ii].Attributes.Add(attribute); + } + } + + // deallocate structure. + if (deallocate) + { + Marshal.DestroyStructure(pos, typeof(OpcRcw.Ae.OPCCONDITIONSTATE)); + } + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Ae.OPCCONDITIONSTATE))); + } + + // deallocate array. + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /* + /// + /// Converts an array of COM HRESULTs structures to .NET ResultID objects. + /// + internal static ResultID[] GetResultIDs(ref IntPtr pInput, int count, bool deallocate) + { + ResultID[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new ResultID[count]; + + int[] errors = OpcCom.Interop.GetInt32s(ref pInput, count, deallocate); + + for (int ii = 0; ii < count; ii++) + { + output[ii] = OpcCom.Interop.GetResultID(errors[ii]); + } + } + + return output; + } + + /// + /// Converts an array of COM SourceServer structures to .NET SourceServer objects. + /// + internal static SourceServer[] GetSourceServers(ref IntPtr pInput, int count, bool deallocate) + { + SourceServer[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new SourceServer[count]; + + IntPtr pos = pInput; + + for (int ii = 0; ii < count; ii++) + { + OpcRcw.Dx.SourceServer server = (OpcRcw.Dx.SourceServer)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.SourceServer)); + + output[ii] = new SourceServer(); + + output[ii].ItemName = server.szItemName; + output[ii].ItemPath = server.szItemPath; + output[ii].Version = server.szVersion; + output[ii].Name = server.szName; + output[ii].Description = server.szDescription; + output[ii].ServerType = server.szServerType; + output[ii].ServerURL = server.szServerURL; + output[ii].DefaultConnected = server.bDefaultSourceServerConnected != 0; + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.SourceServer))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Converts an array of .NET SourceServer objects to COM SourceServer structures. + /// + internal static OpcRcw.Dx.SourceServer[] GetSourceServers(SourceServer[] input) + { + OpcRcw.Dx.SourceServer[] output = null; + + if (input != null && input.Length > 0) + { + output = new OpcRcw.Dx.SourceServer[input.Length]; + + for (int ii = 0; ii < input.Length; ii++) + { + output[ii] = new OpcRcw.Dx.SourceServer(); + + output[ii].dwMask = (uint)OpcRcw.Dx.Mask.All; + output[ii].szItemName = input[ii].ItemName; + output[ii].szItemPath = input[ii].ItemPath; + output[ii].szVersion = input[ii].Version; + output[ii].szName = input[ii].Name; + output[ii].szDescription = input[ii].Description; + output[ii].szServerType = input[ii].ServerType; + output[ii].szServerURL = input[ii].ServerURL; + output[ii].bDefaultSourceServerConnected = (input[ii].DefaultConnected)?1:0; + } + } + + return output; + } + + /// + /// Converts an array of COM DXGeneralResponse structure to a .NET GeneralResponse object. + /// + internal static GeneralResponse GetGeneralResponse(OpcRcw.Dx.DXGeneralResponse input, bool deallocate) + { + Opc.Dx.IdentifiedResult[] results = Interop.GetIdentifiedResults(ref input.pIdentifiedResults, input.dwCount, deallocate); + + return new GeneralResponse(input.szConfigurationVersion, results); + } + + /// + /// Converts an array of COM IdentifiedResult structures to .NET IdentifiedResult objects. + /// + internal static Opc.Dx.IdentifiedResult[] GetIdentifiedResults(ref IntPtr pInput, int count, bool deallocate) + { + Opc.Dx.IdentifiedResult[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new Opc.Dx.IdentifiedResult[count]; + + IntPtr pos = pInput; + + for (int ii = 0; ii < count; ii++) + { + OpcRcw.Dx.IdentifiedResult result = (OpcRcw.Dx.IdentifiedResult)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.IdentifiedResult)); + + output[ii] = new Opc.Dx.IdentifiedResult(); + + output[ii].ItemName = result.szItemName; + output[ii].ItemPath = result.szItemPath; + output[ii].Version = result.szVersion; + output[ii].ResultID = OpcCom.Interop.GetResultID(result.hResultCode); + + if (deallocate) + { + Marshal.DestroyStructure(pos, typeof(OpcRcw.Dx.IdentifiedResult)); + } + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.IdentifiedResult))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Converts an array of COM DXConnection structures to .NET DXConnection objects. + /// + internal static DXConnection[] GetDXConnections(ref IntPtr pInput, int count, bool deallocate) + { + DXConnection[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new DXConnection[count]; + + IntPtr pos = pInput; + + for (int ii = 0; ii < count; ii++) + { + OpcRcw.Dx.DXConnection connection = (OpcRcw.Dx.DXConnection)Marshal.PtrToStructure(pos, typeof(OpcRcw.Dx.DXConnection)); + + output[ii] = GetDXConnection(connection, deallocate); + + if (deallocate) + { + Marshal.DestroyStructure(pos, typeof(OpcRcw.Dx.DXConnection)); + } + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Dx.DXConnection))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Converts an array of .NET DXConnection objects to COM DXConnection structures. + /// + internal static OpcRcw.Dx.DXConnection[] GetDXConnections(DXConnection[] input) + { + OpcRcw.Dx.DXConnection[] output = null; + + if (input != null && input.Length > 0) + { + output = new OpcRcw.Dx.DXConnection[input.Length]; + + for (int ii = 0; ii < input.Length; ii++) + { + output[ii] = GetDXConnection(input[ii]); + } + } + + return output; + } + + /// + /// Converts a .NET DXConnection object to COM DXConnection structure. + /// + internal static OpcRcw.Dx.DXConnection GetDXConnection(DXConnection input) + { + OpcRcw.Dx.DXConnection output = new OpcRcw.Dx.DXConnection(); + + // set output default values. + output.dwMask = 0; + output.szItemPath = null; + output.szItemName = null; + output.szVersion = null; + output.dwBrowsePathCount = 0; + output.pszBrowsePaths = IntPtr.Zero; + output.szName = null; + output.szDescription = null; + output.szKeyword = null; + output.bDefaultSourceItemConnected = 0; + output.bDefaultTargetItemConnected = 0; + output.bDefaultOverridden = 0; + output.vDefaultOverrideValue = null; + output.vSubstituteValue = null; + output.bEnableSubstituteValue = 0; + output.szTargetItemPath = null; + output.szTargetItemName = null; + output.szSourceServerName = null; + output.szSourceItemPath = null; + output.szSourceItemName = null; + output.dwSourceItemQueueSize = 0; + output.dwUpdateRate = 0; + output.fltDeadBand = 0; + output.szVendorData = null; + + // item name + if (input.ItemName != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.ItemName; + output.szItemName = input.ItemName; + } + + // item path + if (input.ItemPath != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.ItemPath; + output.szItemPath = input.ItemPath; + } + + // version + if (input.Version != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.Version; + output.szVersion = input.Version; + } + + // browse paths + if (input.BrowsePaths.Count > 0) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.BrowsePaths; + output.dwBrowsePathCount = input.BrowsePaths.Count; + output.pszBrowsePaths = OpcCom.Interop.GetUnicodeStrings(input.BrowsePaths.ToArray()); + } + + // name + if (input.Name != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.Name; + output.szName = input.Name; + } + + // description + if (input.Description != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.Description; + output.szDescription = input.Description; + } + + // keyword + if (input.Keyword != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.Keyword; + output.szKeyword = input.Keyword; + } + + // default source item connected + if (input.DefaultSourceItemConnectedSpecified) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultSourceItemConnected; + output.bDefaultSourceItemConnected = (input.DefaultSourceItemConnected)?1:0; + } + + // default target item connected + if (input.DefaultTargetItemConnectedSpecified) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultTargetItemConnected; + output.bDefaultTargetItemConnected = (input.DefaultTargetItemConnected)?1:0; + } + + // default overridden + if (input.DefaultOverriddenSpecified) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultOverridden; + output.bDefaultOverridden = (input.DefaultOverridden)?1:0; + } + + // default override value + if (input.DefaultOverrideValue != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.DefaultOverrideValue; + output.vDefaultOverrideValue = input.DefaultOverrideValue; + } + + // substitute value + if (input.SubstituteValue != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.SubstituteValue; + output.vSubstituteValue = input.SubstituteValue; + } + + // enable substitute value + if (input.EnableSubstituteValueSpecified) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.EnableSubstituteValue; + output.bEnableSubstituteValue = (input.EnableSubstituteValue)?1:0; + } + + // target item name + if (input.TargetItemName != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.TargetItemName; + output.szTargetItemName = input.TargetItemName; + } + + // target item path + if (input.TargetItemPath != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.TargetItemPath; + output.szTargetItemPath = input.TargetItemPath; + } + + // source server name + if (input.SourceServerName != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceServerName; + output.szSourceServerName = input.SourceServerName; + } + + // source item name + if (input.SourceItemName != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemName; + output.szSourceItemName = input.SourceItemName; + } + + // source item path + if (input.SourceItemPath != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemPath; + output.szSourceItemPath = input.SourceItemPath; + } + + // source item queue size + if (input.SourceItemQueueSizeSpecified) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.SourceItemQueueSize; + output.dwSourceItemQueueSize = input.SourceItemQueueSize; + } + + // update rate + if (input.UpdateRateSpecified) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.UpdateRate; + output.dwUpdateRate = input.UpdateRate; + } + + // deadband + if (input.DeadbandSpecified) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.DeadBand; + output.fltDeadBand = input.Deadband; + } + + // vendor data + if (input.VendorData != null) + { + output.dwMask |= (uint)OpcRcw.Dx.Mask.VendorData; + output.szVendorData = input.VendorData; + } + + return output; + } + + /// + /// Converts a COM DXConnection structure to a .NET DXConnection object. + /// + internal static DXConnection GetDXConnection(OpcRcw.Dx.DXConnection input, bool deallocate) + { + DXConnection output = new DXConnection(); + + // set output default values. + output.ItemPath = null; + output.ItemName = null; + output.Version = null; + output.BrowsePaths.Clear(); + output.Name = null; + output.Description = null; + output.Keyword = null; + output.DefaultSourceItemConnected = false; + output.DefaultSourceItemConnectedSpecified = false; + output.DefaultTargetItemConnected = false; + output.DefaultTargetItemConnectedSpecified = false; + output.DefaultOverridden = false; + output.DefaultOverriddenSpecified = false; + output.DefaultOverrideValue = null; + output.SubstituteValue = null; + output.EnableSubstituteValue = false; + output.EnableSubstituteValueSpecified = false; + output.TargetItemPath = null; + output.TargetItemName = null; + output.SourceServerName = null; + output.SourceItemPath = null; + output.SourceItemName = null; + output.SourceItemQueueSize = 0; + output.SourceItemQueueSizeSpecified = false; + output.UpdateRate = 0; + output.UpdateRateSpecified = false; + output.Deadband = 0; + output.DeadbandSpecified = false; + output.VendorData = null; + + // item name + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.ItemName) != 0) + { + output.ItemName = input.szItemName; + } + + // item path + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.ItemPath) != 0) + { + output.ItemPath = input.szItemPath; + } + + // version + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Version) != 0) + { + output.Version = input.szVersion; + } + + // browse paths + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.BrowsePaths) != 0) + { + string[] browsePaths = OpcCom.Interop.GetUnicodeStrings(ref input.pszBrowsePaths, input.dwBrowsePathCount, deallocate); + + if (browsePaths != null) + { + output.BrowsePaths.AddRange(browsePaths); + } + } + + // name + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Name) != 0) + { + output.Name = input.szName; + } + + // description + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Description) != 0) + { + output.Description = input.szDescription; + } + + // keyword + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.Keyword) != 0) + { + output.Keyword = input.szKeyword; + } + + // default source item connected + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultSourceItemConnected) != 0) + { + output.DefaultSourceItemConnected = input.bDefaultSourceItemConnected != 0; + output.DefaultSourceItemConnectedSpecified = true; + } + + // default target item connected + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultTargetItemConnected) != 0) + { + output.DefaultTargetItemConnected = input.bDefaultTargetItemConnected != 0; + output.DefaultTargetItemConnectedSpecified = true; + } + + // default overridden + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultOverridden) != 0) + { + output.DefaultOverridden = input.bDefaultOverridden != 0; + output.DefaultOverriddenSpecified = true; + } + + // default override value + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DefaultOverrideValue) != 0) + { + output.DefaultOverrideValue = input.vDefaultOverrideValue; + } + + // substitute value + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SubstituteValue) != 0) + { + output.SubstituteValue = input.vSubstituteValue; + } + + // enable substitute value + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.EnableSubstituteValue) != 0) + { + output.EnableSubstituteValue = input.bEnableSubstituteValue != 0; + output.EnableSubstituteValueSpecified = true; + } + + // target item name + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.TargetItemName) != 0) + { + output.TargetItemName = input.szTargetItemName; + } + + // target item path + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.TargetItemPath) != 0) + { + output.TargetItemPath = input.szTargetItemPath; + } + + // source server name + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceServerName) != 0) + { + output.SourceServerName = input.szSourceServerName; + } + + // source item name + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemName) != 0) + { + output.SourceItemName = input.szSourceItemName; + } + + // source item path + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemPath) != 0) + { + output.SourceItemPath = input.szSourceItemPath; + } + + // source item queue size + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.SourceItemQueueSize) != 0) + { + output.SourceItemQueueSize = input.dwSourceItemQueueSize; + output.SourceItemQueueSizeSpecified = true; + } + + // update rate + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.UpdateRate) != 0) + { + output.UpdateRate = input.dwUpdateRate; + output.UpdateRateSpecified = true; + } + + // deadband + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.DeadBand) != 0) + { + output.Deadband = input.fltDeadBand; + output.DeadbandSpecified = true; + } + + // vendor data + if ((input.dwMask & (uint)OpcRcw.Dx.Mask.VendorData) != 0) + { + output.VendorData = input.szVendorData; + } + + return output; + } + + /// + /// Converts an array of .NET ItemIdentifier objects to COM ItemIdentifier structures. + /// + internal static OpcRcw.Dx.ItemIdentifier[] GetItemIdentifiers(Opc.Dx.ItemIdentifier[] input) + { + OpcRcw.Dx.ItemIdentifier[] output = null; + + if (input != null && input.Length > 0) + { + output = new OpcRcw.Dx.ItemIdentifier[input.Length]; + + for (int ii = 0; ii < input.Length; ii++) + { + output[ii] = new OpcRcw.Dx.ItemIdentifier(); + + output[ii].szItemName = input[ii].ItemName; + output[ii].szItemPath = input[ii].ItemPath; + output[ii].szVersion = input[ii].Version; + } + } + + return output; + } + */ + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Ae/Result.cs b/Technosoftware/DaAeHdaClient.Com/Ae/Result.cs new file mode 100644 index 0000000..d6ee3c6 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Ae/Result.cs @@ -0,0 +1,51 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Ae +{ + /// + /// Defines all well known COM AE HRESULT codes. + /// + internal struct Result + { + /// + public const int S_ALREADYACKED = +0x00040200; // 0x00040200 + /// + public const int S_INVALIDBUFFERTIME = +0x00040201; // 0x00040201 + /// + public const int S_INVALIDMAXSIZE = +0x00040202; // 0x00040202 + /// + public const int S_INVALIDKEEPALIVETIME = +0x00040203; // 0x00040203 + /// + public const int E_INVALIDBRANCHNAME = -0x3FFBFDFD; // 0xC0040203 + /// + public const int E_INVALIDTIME = -0x3FFBFDFC; // 0xC0040204 + /// + public const int E_BUSY = -0x3FFBFDFB; // 0xC0040205 + /// + public const int E_NOINFO = -0x3FFBFDFA; // 0xC0040206 + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Ae/Server.cs b/Technosoftware/DaAeHdaClient.Com/Ae/Server.cs new file mode 100644 index 0000000..1084114 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Ae/Server.cs @@ -0,0 +1,1575 @@ +#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; +using System.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Ae; +using Technosoftware.OpcRcw.Comn; +using Technosoftware.OpcRcw.Ae; +#endregion + +#pragma warning disable 0618 + +namespace Technosoftware.DaAeHdaClient.Com.Ae +{ + /// + /// A .NET wrapper for a COM server that implements the AE server interfaces. + /// + [Serializable] + internal class Server : Technosoftware.DaAeHdaClient.Com.Server, ITsCAeServer + { + #region Constructors + /// + /// Initializes the object with the specified OpcUrl and COM server. + /// + internal Server(OpcUrl url, object server) : base(url, server) + { + m_supportsAE11 = true; + + // check if the V1.1 interfaces are supported. + try + { + var server2 = (IOPCEventServer2)server; + } + catch + { + m_supportsAE11 = false; + } + } + #endregion + + #region IDisposable Members + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected override void Dispose(bool disposing) + { + if (!m_disposed) + { + lock (this) + { + if (disposing) + { + // Release managed resources. + + // release the server. + if (server_ != null) + { + // release all subscriptions. + foreach (Subscription subscription in m_subscriptions.Values) + { + // dispose of the subscription object (disconnects all subscriptions connections). + subscription.Dispose(); + } + + // clear subscription table. + m_subscriptions.Clear(); + } + } + + // Release unmanaged resources. + // Set large fields to null. + + // release the browser. + if (m_browser != null) + { + Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(m_browser); + m_browser = null; + } + + // release the server. + if (server_ != null) + { + Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(server_); + server_ = null; + } + } + + // Call Dispose on your base class. + m_disposed = true; + } + + base.Dispose(disposing); + } + + private bool m_disposed = false; + #endregion + + #region Technosoftware.DaAeHdaClient.IOpcServer Members + //====================================================================== + // Get Status + + /// + /// Returns the current server status. + /// + /// The current server status. + public OpcServerStatus GetServerStatus() + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCServer.GetStatus"; + + // initialize arguments. + var pStatus = IntPtr.Zero; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + ((IOPCEventServer)server_).GetStatus(out pStatus); + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("Server.GetStatus", e); + } + finally + { + EndComCall(methodName); + } + + // return results. + return Interop.GetServerStatus(ref pStatus, true); + } + } + + //====================================================================== + // Event Subscription + + /// + /// Creates a new event subscription. + /// + /// The initial state for the subscription. + /// The new subscription object. + public ITsCAeSubscription CreateSubscription(TsCAeSubscriptionState state) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + if (state == null) throw new ArgumentNullException(nameof(state)); + + // initialize arguments. + object unknown = null; + var riid = typeof(IOPCEventSubscriptionMgt).GUID; + var bufferTime = 0; + var maxSize = 0; + + // invoke COM method. + try + { + ((IOPCEventServer)server_).CreateEventSubscription( + (state.Active)?1:0, + state.BufferTime, + state.MaxSize, + ++m_handles, + ref riid, + out unknown, + out bufferTime, + out maxSize); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.CreateEventSubscription", e); + } + + // save actual values. + state.BufferTime = bufferTime; + state.MaxSize = maxSize; + + var subscription = new Subscription(state, unknown); + + // set keep alive. + subscription.ModifyState((int)TsCAeStateMask.KeepAlive, state); + + // save subscription. + m_subscriptions.Add(m_handles, subscription); + + // return results. + return subscription; + } + } + + //====================================================================== + // QueryAvailableFilters + + /// + /// Returns the event filters supported by the server. + /// + /// A bit mask of all event filters supported by the server. + public int QueryAvailableFilters() + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var filters = 0; + + // invoke COM method. + try + { + ((IOPCEventServer)server_).QueryAvailableFilters(out filters); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.QueryAvailableFilters", e); + } + + // return results. + return filters; + } + } + + //====================================================================== + // QueryEventCategories + + /// + /// Returns the event categories supported by the server for the specified event types. + /// + /// A bit mask for the event types of interest. + /// A collection of event categories. + public TsCAeCategory[] QueryEventCategories(int eventType) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = 0; + + var ppdwEventCategories = IntPtr.Zero; + var ppszEventCategoryDescs = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCEventServer)server_).QueryEventCategories( + eventType, + out count, + out ppdwEventCategories, + out ppszEventCategoryDescs); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.QueryEventCategories", e); + } + + // check for empty list. + if (count == 0) + { + return new TsCAeCategory[0]; + } + + // unmarshal arguments. + var ids = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppdwEventCategories, count, true); + var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppszEventCategoryDescs, count, true); + + // build results. + var categories = new TsCAeCategory[count]; + + for (var ii = 0; ii < count; ii++) + { + categories[ii] = new TsCAeCategory(); + + categories[ii].ID = ids[ii]; + categories[ii].Name = names[ii]; + } + + // return results. + return categories; + } + } + + //====================================================================== + // QueryConditionNames + + /// + /// Returns the condition names supported by the server for the specified event categories. + /// + /// A bit mask for the event categories of interest. + /// A list of condition names. + public string[] QueryConditionNames(int eventCategory) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = 0; + var ppszConditionNames = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCEventServer)server_).QueryConditionNames( + eventCategory, + out count, + out ppszConditionNames); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.QueryConditionNames", e); + } + + // check for empty list. + if (count == 0) + { + return new string[0]; + } + + // unmarshal arguments. + var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppszConditionNames, count, true); + + // return results. + return names; + } + } + + //====================================================================== + // QuerySubConditionNames + + /// + /// Returns the sub-condition names supported by the server for the specified event condition. + /// + /// The name of the condition. + /// A list of sub-condition names. + public string[] QuerySubConditionNames(string conditionName) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = 0; + var ppszSubConditionNames = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCEventServer)server_).QuerySubConditionNames( + conditionName, + out count, + out ppszSubConditionNames); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.QuerySubConditionNames", e); + } + + // check for empty list. + if (count == 0) + { + return new string[0]; + } + + // unmarshal arguments. + var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppszSubConditionNames, count, true); + + // return results. + return names; + } + } + + //====================================================================== + // QuerySourceConditions + + /// + /// Returns the condition names supported by the server for the specified event source. + /// + /// The name of the event source. + /// A list of condition names. + public string[] QueryConditionNames(string sourceName) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = 0; + var ppszConditionNames = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCEventServer)server_).QuerySourceConditions( + sourceName, + out count, + out ppszConditionNames); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.QuerySourceConditions", e); + } + + // check for empty list. + if (count == 0) + { + return new string[0]; + } + + // unmarshal arguments. + var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppszConditionNames, count, true); + + // return results. + return names; + } + } + + //====================================================================== + // QueryEventAttributes + + /// + /// Returns the event attributes supported by the server for the specified event categories. + /// + /// A bit mask for the event categories of interest. + /// A collection of event attributes. + public TsCAeAttribute[] QueryEventAttributes(int eventCategory) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = 0; + var ppdwAttrIDs = IntPtr.Zero; + var ppszAttrDescs = IntPtr.Zero; + var ppvtAttrTypes = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCEventServer)server_).QueryEventAttributes( + eventCategory, + out count, + out ppdwAttrIDs, + out ppszAttrDescs, + out ppvtAttrTypes); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.QueryEventAttributes", e); + } + + // check for empty list. + if (count == 0) + { + return new TsCAeAttribute[0]; + } + + // unmarshal arguments. + var ids = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppdwAttrIDs, count, true); + var names = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppszAttrDescs, count, true); + var types = Technosoftware.DaAeHdaClient.Com.Interop.GetInt16s(ref ppvtAttrTypes, count, true); + + // build results. + var attributes = new TsCAeAttribute[count]; + + for (var ii = 0; ii < count; ii++) + { + attributes[ii] = new TsCAeAttribute(); + + attributes[ii].ID = ids[ii]; + attributes[ii].Name = names[ii]; + attributes[ii].DataType = Technosoftware.DaAeHdaClient.Com.Interop.GetType((VarEnum)types[ii]); + } + + // return results. + return attributes; + } + } + + //====================================================================== + // TranslateToItemIDs + + /// + /// Returns the DA item ids for a set of attribute ids belonging to events which meet the specified filter criteria. + /// + /// The event source of interest. + /// The id of the event category for the events of interest. + /// The name of a condition within the event category. + /// The name of a sub-condition within a multi-state condition. + /// The ids of the attributes to return item ids for. + /// A list of item urls for each specified attribute. + public TsCAeItemUrl[] TranslateToItemIDs( + string sourceName, + int eventCategory, + string conditionName, + string subConditionName, + int[] attributeIDs) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var ppszAttrItemIDs = IntPtr.Zero; + var ppszNodeNames = IntPtr.Zero; + var ppCLSIDs = IntPtr.Zero; + + var count = (attributeIDs != null)?attributeIDs.Length:0; + + // call server. + try + { + ((IOPCEventServer)server_).TranslateToItemIDs( + (sourceName != null)?sourceName:"", + eventCategory, + (conditionName != null)?conditionName:"", + (subConditionName != null)?subConditionName:"", + count, + (count > 0)?attributeIDs:new int[0], + out ppszAttrItemIDs, + out ppszNodeNames, + out ppCLSIDs); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.TranslateToItemIDs", e); + } + + // unmarshal results. + var itemIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppszAttrItemIDs, count, true); + var nodeNames = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppszNodeNames, count, true); + var clsids = Technosoftware.DaAeHdaClient.Com.Interop.GetGUIDs(ref ppCLSIDs, count, true); + + var itemUrls = new TsCAeItemUrl[count]; + + // fill in item urls. + for (var ii = 0; ii < count; ii++) + { + itemUrls[ii] = new TsCAeItemUrl(); + + itemUrls[ii].ItemName = itemIDs[ii]; + itemUrls[ii].ItemPath = null; + itemUrls[ii].Url.Scheme = OpcUrlScheme.DA; + itemUrls[ii].Url.HostName = nodeNames[ii]; + itemUrls[ii].Url.Path = string.Format("{{{0}}}", clsids[ii]); + } + + // return results. + return itemUrls; + } + } + + //====================================================================== + // GetConditionState + + /// + /// Returns the current state information for the condition instance corresponding to the source and condition name. + /// + /// The source name + /// A condition name for the source. + /// The list of attributes to return with the condition state. + /// The current state of the connection. + public TsCAeCondition GetConditionState( + string sourceName, + string conditionName, + int[] attributeIDs) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var ppConditionState = IntPtr.Zero; + + // call server. + try + { + ((IOPCEventServer)server_).GetConditionState( + (sourceName != null)?sourceName:"", + (conditionName != null)?conditionName:"", + (attributeIDs != null)?attributeIDs.Length:0, + (attributeIDs != null)?attributeIDs:new int[0], + out ppConditionState); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.GetConditionState", e); + } + + // unmarshal results. + var conditions = Interop.GetConditions(ref ppConditionState, 1, true); + + // fill in attribute ids. + for (var ii = 0; ii < conditions[0].Attributes.Count; ii++) + { + conditions[0].Attributes[ii].ID = attributeIDs[ii]; + } + + // return results. + return conditions[0]; + } + } + + //====================================================================== + // EnableConditionByArea + + /// + /// Places the specified process areas into the enabled state. + /// + /// A list of fully qualified area names. + /// The results of the operation for each area. + public OpcResult[] EnableConditionByArea(string[] areas) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // check for trivial case. + if (areas == null || areas.Length == 0) + { + return new OpcResult[0]; + } + + // initialize arguments. + var ppErrors = IntPtr.Zero; + + int[] errors = null; + + if (m_supportsAE11) + { + try + { + ((IOPCEventServer2)server_).EnableConditionByArea2( + areas.Length, + areas, + out ppErrors); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer2.EnableConditionByArea2", e); + } + + // unmarshal arguments. + errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppErrors, areas.Length, true); + } + else + { + try + { + ((IOPCEventServer)server_).EnableConditionByArea( + areas.Length, + areas); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.EnableConditionByArea", e); + } + + // create dummy error array (0 == S_OK). + errors = new int[areas.Length]; + } + + // build results. + var results = new OpcResult[errors.Length]; + + for (var ii = 0; ii < errors.Length; ii++) + { + results[ii] = Interop.GetResultID(errors[ii]); + } + + // return results. + return results; + } + } + + //====================================================================== + // DisableConditionByArea + + /// + /// Places the specified process areas into the disabled state. + /// + /// A list of fully qualified area names. + /// The results of the operation for each area. + public OpcResult[] DisableConditionByArea(string[] areas) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // check for trivial case. + if (areas == null || areas.Length == 0) + { + return new OpcResult[0]; + } + + // initialize arguments. + var ppErrors = IntPtr.Zero; + + int[] errors = null; + + if (m_supportsAE11) + { + try + { + ((IOPCEventServer2)server_).DisableConditionByArea2( + areas.Length, + areas, + out ppErrors); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer2.DisableConditionByArea2", e); + } + + // unmarshal arguments. + errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppErrors, areas.Length, true); + } + else + { + try + { + ((IOPCEventServer)server_).DisableConditionByArea( + areas.Length, + areas); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.DisableConditionByArea", e); + } + + // create dummy error array (0 == S_OK). + errors = new int[areas.Length]; + } + + // build results. + var results = new OpcResult[errors.Length]; + + for (var ii = 0; ii < errors.Length; ii++) + { + results[ii] = Interop.GetResultID(errors[ii]); + } + + // return results. + return results; + } + } + + //====================================================================== + // EnableConditionBySource + + /// + /// Places the specified process areas into the enabled state. + /// + /// A list of fully qualified source names. + /// The results of the operation for each area. + public OpcResult[] EnableConditionBySource(string[] sources) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // check for trivial case. + if (sources == null || sources.Length == 0) + { + return new OpcResult[0]; + } + + // initialize arguments. + var ppErrors = IntPtr.Zero; + + int[] errors = null; + + if (m_supportsAE11) + { + try + { + ((IOPCEventServer2)server_).EnableConditionBySource2( + sources.Length, + sources, + out ppErrors); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer2.EnableConditionBySource2", e); + } + + // unmarshal arguments. + errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppErrors, sources.Length, true); + } + else + { + try + { + ((IOPCEventServer)server_).EnableConditionBySource( + sources.Length, + sources); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.EnableConditionBySource", e); + } + + // create dummy error array (0 == S_OK). + errors = new int[sources.Length]; + } + + // build results. + var results = new OpcResult[errors.Length]; + + for (var ii = 0; ii < errors.Length; ii++) + { + results[ii] = Interop.GetResultID(errors[ii]); + } + + // return results. + return results; + } + } + + //====================================================================== + // DisableConditionBySource + + /// + /// Places the specified process areas into the disabled state. + /// + /// A list of fully qualified source names. + /// The results of the operation for each area. + public OpcResult[] DisableConditionBySource(string[] sources) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // check for trivial case. + if (sources == null || sources.Length == 0) + { + return new OpcResult[0]; + } + + // initialize arguments. + var ppErrors = IntPtr.Zero; + + int[] errors = null; + + if (m_supportsAE11) + { + try + { + ((IOPCEventServer2)server_).DisableConditionBySource2( + sources.Length, + sources, + out ppErrors); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer2.DisableConditionBySource2", e); + } + + // unmarshal arguments. + errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppErrors, sources.Length, true); + } + else + { + try + { + ((IOPCEventServer)server_).DisableConditionBySource( + sources.Length, + sources); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.DisableConditionBySource", e); + } + + // create dummy error array (0 == S_OK). + errors = new int[sources.Length]; + } + + // build results. + var results = new OpcResult[errors.Length]; + + for (var ii = 0; ii < errors.Length; ii++) + { + results[ii] = Interop.GetResultID(errors[ii]); + } + + // return results. + return results; + } + } + + //====================================================================== + // GetEnableStateByArea + + /// + /// Returns the enabled state for the specified process areas. + /// + /// A list of fully qualified area names. + public TsCAeEnabledStateResult[] GetEnableStateByArea(string[] areas) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // check for trivial case. + if (areas == null || areas.Length == 0) + { + return new TsCAeEnabledStateResult[0]; + } + + // return error code if AE 1.1 not supported. + if (!m_supportsAE11) + { + var failures = new TsCAeEnabledStateResult[areas.Length]; + + for (var ii = 0; ii < failures.Length; ii++) + { + failures[ii] = new TsCAeEnabledStateResult(); + + failures[ii].Enabled = false; + failures[ii].EffectivelyEnabled = false; + failures[ii].Result = OpcResult.E_FAIL; + } + + return failures; + } + + // initialize arguments. + var pbEnabled = IntPtr.Zero; + var pbEffectivelyEnabled = IntPtr.Zero; + var ppErrors = IntPtr.Zero; + + try + { + ((IOPCEventServer2)server_).GetEnableStateByArea( + areas.Length, + areas, + out pbEnabled, + out pbEffectivelyEnabled, + out ppErrors); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer2.GetEnableStateByArea", e); + } + + // unmarshal arguments. + var enabled = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pbEnabled, areas.Length, true); + var effectivelyEnabled = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pbEffectivelyEnabled, areas.Length, true); + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppErrors, areas.Length, true); + + + // build results. + var results = new TsCAeEnabledStateResult[errors.Length]; + + for (var ii = 0; ii < errors.Length; ii++) + { + results[ii] = new TsCAeEnabledStateResult(); + + results[ii].Enabled = enabled[ii] != 0; + results[ii].EffectivelyEnabled = effectivelyEnabled[ii] != 0; + results[ii].Result = Interop.GetResultID(errors[ii]); + } + + // return results + return results; + } + } + + //====================================================================== + // GetEnableStateBySource + + /// + /// Returns the enabled state for the specified event sources. + /// + /// A list of fully qualified source names. + public TsCAeEnabledStateResult[] GetEnableStateBySource(string[] sources) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // check for trivial case. + if (sources == null || sources.Length == 0) + { + return new TsCAeEnabledStateResult[0]; + } + + // return error code if AE 1.1 not supported. + if (!m_supportsAE11) + { + var failures = new TsCAeEnabledStateResult[sources.Length]; + + for (var ii = 0; ii < failures.Length; ii++) + { + failures[ii] = new TsCAeEnabledStateResult(); + + failures[ii].Enabled = false; + failures[ii].EffectivelyEnabled = false; + failures[ii].Result = OpcResult.E_FAIL; + } + + return failures; + } + + // initialize arguments. + var pbEnabled = IntPtr.Zero; + var pbEffectivelyEnabled = IntPtr.Zero; + var ppErrors = IntPtr.Zero; + + try + { + ((IOPCEventServer2)server_).GetEnableStateBySource( + sources.Length, + sources, + out pbEnabled, + out pbEffectivelyEnabled, + out ppErrors); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer2.GetEnableStateBySource", e); + } + + // unmarshal arguments. + var enabled = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pbEnabled, sources.Length, true); + var effectivelyEnabled = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pbEffectivelyEnabled, sources.Length, true); + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppErrors, sources.Length, true); + + + // build results. + var results = new TsCAeEnabledStateResult[errors.Length]; + + for (var ii = 0; ii < errors.Length; ii++) + { + results[ii] = new TsCAeEnabledStateResult(); + + results[ii].Enabled = enabled[ii] != 0; + results[ii].EffectivelyEnabled = effectivelyEnabled[ii] != 0; + results[ii].Result = Interop.GetResultID(errors[ii]); + } + + // return results + return results; + } + } + + //====================================================================== + // AcknowledgeCondition + + /// + /// Used to acknowledge one or more conditions in the event server. + /// + /// The identifier for who is acknowledging the condition. + /// A comment associated with the acknowledgment. + /// The conditions being acknowledged. + /// A list of result id indictaing whether each condition was successfully acknowledged. + public OpcResult[] AcknowledgeCondition( + string acknowledgerID, + string comment, + TsCAeEventAcknowledgement[] conditions) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // check for trivial case. + if (conditions == null || conditions.Length == 0) + { + return new OpcResult[0]; + } + + // initialize arguments. + var count = conditions.Length; + + var pszSource = new string[count]; + var pszConditionName = new string[count]; + var pftActiveTime = new OpcRcw.Ae.FILETIME[count]; + var pdwCookie = new int[count]; + + for (var ii = 0; ii < count; ii ++) + { + pszSource[ii] = conditions[ii].SourceName; + pszConditionName[ii] = conditions[ii].ConditionName; + pftActiveTime[ii] = Interop.Convert(Com.Interop.GetFILETIME(conditions[ii].ActiveTime)); + pdwCookie[ii] = conditions[ii].Cookie; + } + + var ppErrors = IntPtr.Zero; + + // call server. + try + { + ((IOPCEventServer)server_).AckCondition( + conditions.Length, + acknowledgerID, + comment, + pszSource, + pszConditionName, + pftActiveTime, + pdwCookie, + out ppErrors); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.AckCondition", e); + } + + // unmarshal results. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppErrors, count, true); + + // build results. + var results = new OpcResult[count]; + + for (var ii = 0; ii < count; ii++) + { + results[ii] = Interop.GetResultID(errors[ii]); + } + + // return results. + return results; + } + } + + //====================================================================== + // Browse + + /// + /// Browses for all children of the specified area that meet the filter criteria. + /// + /// The full-qualified id for the area. + /// The type of children to return. + /// The expression used to filter the names of children returned. + /// The set of elements that meet the filter criteria. + public TsCAeBrowseElement[] Browse( + string areaID, + TsCAeBrowseType browseType, + string browseFilter) + { + lock (this) + { + // intialize arguments. + IOpcBrowsePosition position = null; + + // browse for all elements at the current position. + var elements = Browse(areaID, browseType, browseFilter, 0, out position); + + // free position object. + if (position != null) + { + position.Dispose(); + } + + // return results. + return elements; + } + } + + /// + /// Browses for all children of the specified area that meet the filter criteria. + /// + /// The full-qualified id for the area. + /// The type of children to return. + /// The expression used to filter the names of children returned. + /// The maximum number of elements to return. + /// The object used to continue the browse if the number nodes exceeds the maximum specified. + /// The set of elements that meet the filter criteria. + public TsCAeBrowseElement[] Browse( + string areaID, + TsCAeBrowseType browseType, + string browseFilter, + int maxElements, + out IOpcBrowsePosition position) + { + position = null; + + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + + // initialize browser. + InitializeBrowser(); + + // move to desired area. + ChangeBrowsePosition(areaID); + + // create enumerator. + var enumerator = (System.Runtime.InteropServices.ComTypes.IEnumString)CreateEnumerator(browseType, browseFilter); + + // fetch elements. + var elements = new ArrayList(); + + var result = FetchElements(browseType, maxElements, enumerator, elements); + + // dispose of enumerator if all done. + if (result != 0) + { + Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(enumerator); + } + + // create continuation point. + else + { + position = new BrowsePosition(areaID, browseType, browseFilter, enumerator); + } + + // return results. + return (TsCAeBrowseElement[])elements.ToArray(typeof(TsCAeBrowseElement)); + } + } + + //====================================================================== + // BrowseNext + + /// + /// Continues browsing the server's address space at the specified position. + /// + /// The maximum number of elements to return. + /// The position object used to continue a browse operation. + /// The set of elements that meet the filter criteria. + public TsCAeBrowseElement[] BrowseNext(int maxElements, ref IOpcBrowsePosition position) + { + lock (this) + { + // verify state and arguments. + if (server_ == null) throw new NotConnectedException(); + if (position == null) throw new ArgumentNullException(nameof(position)); + + // initialize browser. + InitializeBrowser(); + + // move to desired area. + ChangeBrowsePosition(((BrowsePosition)position).AreaID); + + // fetch enumerator from position object. + var enumerator = ((BrowsePosition)position).Enumerator; + + // fetch elements. + var elements = new ArrayList(); + + var result = FetchElements(((BrowsePosition)position).BrowseType, maxElements, enumerator, elements); + + // dispose of position object if all done. + if (result != 0) + { + position.Dispose(); + position = null; + } + + // return results. + return (TsCAeBrowseElement[])elements.ToArray(typeof(TsCAeBrowseElement)); + } + } + #endregion + + #region Private Methods + /// + /// Creates an area browser object for use by all browse requests. + /// + private void InitializeBrowser() + { + // do nothing if browser already exists. + if (m_browser != null) + { + return; + } + + var riid = typeof(IOPCEventAreaBrowser).GUID; + + + // initialize arguments. + object unknown; + // invoke COM method. + try + { + ((IOPCEventServer)server_).CreateAreaBrowser( + ref riid, + out unknown); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventServer.CreateAreaBrowser", e); + } + + // verify object. + if (unknown == null) + { + throw new OpcResultException(new OpcResult( (int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null ),"The response from the server was invalid or incomplete"); + } + + // save object. + m_browser = unknown; + } + + /// + /// Moves the browse position prior to executing a browse operation. + /// + private void ChangeBrowsePosition(string areaID) + { + var targetID = (areaID != null)?areaID:""; + + // invoke COM method. + try + { + ((IOPCEventAreaBrowser)m_browser).ChangeBrowsePosition( + OPCAEBROWSEDIRECTION.OPCAE_BROWSE_TO, + targetID); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventAreaBrowser.ChangeBrowsePosition", e); + } + } + + /// + /// Creates an enumerator for the names at the current position. + /// + private object CreateEnumerator(TsCAeBrowseType browseType, string browseFilter) + { + // initialize arguments. + var dwBrowseFilterType = Interop.GetBrowseType(browseType); + IEnumString enumerator; + + // invoke COM method. + try + { + ((IOPCEventAreaBrowser)m_browser).BrowseOPCAreas( + dwBrowseFilterType, + (browseFilter != null)?browseFilter:"", + out enumerator); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventAreaBrowser.BrowseOPCAreas", e); + } + + // verify object. + if (enumerator == null) + { + throw new OpcResultException(new OpcResult( (int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null ),"The response from the server was invalid or incomplete"); + } + + // return result. + return (System.Runtime.InteropServices.ComTypes.IEnumString)enumerator; + } + + /// + /// Returns the qualified name for the node at the current position. + /// + private string GetQualifiedName(string name, TsCAeBrowseType browseType) + { + // initialize arguments. + string nodeID; + + // fetch area qualified name. + if (browseType == TsCAeBrowseType.Area) + { + try + { + ((IOPCEventAreaBrowser)m_browser).GetQualifiedAreaName(name, out nodeID); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventAreaBrowser.GetQualifiedAreaName", e); + } + } + + // fetch source qualified name. + else + { + try + { + ((IOPCEventAreaBrowser)m_browser).GetQualifiedSourceName(name, out nodeID); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventAreaBrowser.GetQualifiedSourceName", e); + } + } + + // return results. + return nodeID; + } + + /// + /// Fetches up to max elements and returns an flag indicating whether there are any elements left. + /// + private int FetchElements(TsCAeBrowseType browseType, int maxElements, System.Runtime.InteropServices.ComTypes.IEnumString enumerator, ArrayList elements) + { + var buffer = new string[1]; + + // re-calculate buffer length. + var bufferLength = (maxElements > 0 && maxElements-elements.Count < buffer.Length)?maxElements-elements.Count:buffer.Length; + + // fetch first batch of names. + var pFetched = Marshal.AllocCoTaskMem(sizeof(int)); + + try + { + var result = enumerator.Next(bufferLength, buffer, pFetched); + + while (result == 0) + { + var fetched = Marshal.ReadInt32(pFetched); + + // create elements. + for (var ii = 0; ii < fetched; ii++) + { + var element = new TsCAeBrowseElement(); + + element.Name = buffer[ii]; + element.QualifiedName = GetQualifiedName(buffer[ii], browseType); + element.NodeType = browseType; + + elements.Add(element); + } + + // check for halt. + if (maxElements > 0 && elements.Count >= maxElements) + { + break; + } + + // re-calculate buffer length. + bufferLength = (maxElements > 0 && maxElements-elements.Count < buffer.Length)?maxElements-elements.Count:buffer.Length; + + // fetch next block. + result = enumerator.Next(bufferLength, buffer, pFetched); + } + + // return final result. + return result; + } + finally + { + if (pFetched != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(pFetched); + } + } + } + #endregion + + #region Private Members + private bool m_supportsAE11 = true; + private object m_browser = null; + private int m_handles = 1; + private Hashtable m_subscriptions = new Hashtable(); + #endregion + } + + #region BrowsePosition Class + /// + /// Stores the state of a browse operation. + /// + [Serializable] + internal class BrowsePosition : TsCAeBrowsePosition + { + #region Constructors + /// + /// Saves the parameters for an incomplete browse information. + /// + public BrowsePosition( + string areaID, + TsCAeBrowseType browseType, + string browseFilter, + System.Runtime.InteropServices.ComTypes.IEnumString enumerator) + : + base (areaID, browseType, browseFilter) + { + m_enumerator = enumerator; + } + #endregion + + #region IDisposable Members + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected override void Dispose(bool disposing) + { + if (!m_disposed) + { + if (disposing) + { + // Release managed resources. + } + + // Release unmanaged resources. + // Set large fields to null. + + if (m_enumerator != null) + { + Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(m_enumerator); + m_enumerator = null; + } + + // Call Dispose on your base class. + m_disposed = true; + } + + base.Dispose(disposing); + } + + private bool m_disposed = false; + #endregion + + #region Public Interface + /// + /// Returns the enumerator stored in the object. + /// + public System.Runtime.InteropServices.ComTypes.IEnumString Enumerator => m_enumerator; + + #endregion + + #region Private Members + System.Runtime.InteropServices.ComTypes.IEnumString m_enumerator = null; + #endregion + } + #endregion +} diff --git a/Technosoftware/DaAeHdaClient.Com/Ae/Subscription.cs b/Technosoftware/DaAeHdaClient.Com/Ae/Subscription.cs new file mode 100644 index 0000000..9b9b894 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Ae/Subscription.cs @@ -0,0 +1,610 @@ +#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.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Ae; +using Technosoftware.OpcRcw.Ae; + +using Technosoftware.DaAeHdaClient.Utilities; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Ae +{ + /// + /// A .NET wrapper for a COM server that implements the AE subscription interfaces. + /// + [Serializable] + internal class Subscription : ITsCAeSubscription + { + #region Constructors + /// + /// Initializes the object with the specified URL and COM server. + /// + internal Subscription(TsCAeSubscriptionState state, object subscription) + { + subscription_ = subscription; + clientHandle_ = OpcConvert.Clone(state.ClientHandle); + supportsAe11_ = true; + callback_ = new Callback(state.ClientHandle); + + // check if the V1.1 interfaces are supported. + try + { + var server = (IOPCEventSubscriptionMgt2)subscription_; + } + catch + { + supportsAe11_ = false; + } + } + #endregion + + #region IDisposable Members + /// + /// The finalizer. + /// + ~Subscription() + { + Dispose(false); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!disposed_) + { + lock (lock_) + { + if (disposing) + { + // Free other state (managed objects). + + if (subscription_ != null) + { + // close all connections. + if (connection_ != null) + { + try + { + connection_.Dispose(); + } + catch + { + // Ignore. COM Server probably no longer connected + } + connection_ = null; + } + } + } + + // Free your own state (unmanaged objects). + // Set large fields to null. + + if (subscription_ != null) + { + // release subscription object. + try + { + Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(subscription_); + } + catch + { + // Ignore. COM Server probably no longer connected + } + subscription_ = null; + } + } + + disposed_ = true; + } + } + #endregion + + #region Technosoftware.DaAeHdaClient.ISubscription Members + /// + /// An event to receive data change updates. + /// + public event TsCAeDataChangedEventHandler DataChangedEvent + { + add { lock (this) { Advise(); callback_.DataChangedEvent += value; } } + remove { lock (this) { callback_.DataChangedEvent -= value; Unadvise(); } } + } + + //====================================================================== + // State Management + + /// + /// Returns the current state of the subscription. + /// + /// The current state of the subscription. + public TsCAeSubscriptionState GetState() + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // initialize arguments. + int pbActive; + int pdwBufferTime; + int pdwMaxSize; + var pdwKeepAliveTime = 0; + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).GetState( + out pbActive, + out pdwBufferTime, + out pdwMaxSize, + out _); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetState", e); + } + + // get keep alive. + if (supportsAe11_) + { + try + { + ((IOPCEventSubscriptionMgt2)subscription_).GetKeepAlive(out pdwKeepAliveTime); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt2.GetKeepAlive", e); + } + } + + // build results + var state = new TsCAeSubscriptionState + { + Active = pbActive != 0, + ClientHandle = clientHandle_, + BufferTime = pdwBufferTime, + MaxSize = pdwMaxSize, + KeepAlive = pdwKeepAliveTime + }; + + // return results. + return state; + } + } + + /// + /// Changes the state of a subscription. + /// + /// A bit mask that indicates which elements of the subscription state are changing. + /// The new subscription state. + /// The actual subscription state after applying the changes. + public TsCAeSubscriptionState ModifyState(int masks, TsCAeSubscriptionState state) + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // initialize arguments. + var active = (state.Active) ? 1 : 0; + + var hActive = GCHandle.Alloc(active, GCHandleType.Pinned); + var hBufferTime = GCHandle.Alloc(state.BufferTime, GCHandleType.Pinned); + var hMaxSize = GCHandle.Alloc(state.MaxSize, GCHandleType.Pinned); + + var pbActive = ((masks & (int)TsCAeStateMask.Active) != 0) ? hActive.AddrOfPinnedObject() : IntPtr.Zero; + var pdwBufferTime = ((masks & (int)TsCAeStateMask.BufferTime) != 0) ? hBufferTime.AddrOfPinnedObject() : IntPtr.Zero; + var pdwMaxSize = ((masks & (int)TsCAeStateMask.MaxSize) != 0) ? hMaxSize.AddrOfPinnedObject() : IntPtr.Zero; + + var phClientSubscription = 0; + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).SetState( + pbActive, + pdwBufferTime, + pdwMaxSize, + phClientSubscription, + out _, + out _); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SetState", e); + } + finally + { + if (hActive.IsAllocated) hActive.Free(); + if (hBufferTime.IsAllocated) hBufferTime.Free(); + if (hMaxSize.IsAllocated) hMaxSize.Free(); + } + + // update keep alive. + if (((masks & (int)TsCAeStateMask.KeepAlive) != 0) && supportsAe11_) + { + try + { + ((IOPCEventSubscriptionMgt2)subscription_).SetKeepAlive( + state.KeepAlive, + out _); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt2.SetKeepAlive", e); + } + } + + // return current state. + return GetState(); + } + } + + //====================================================================== + // Filter Management + + /// + /// Returns the current filters for the subscription. + /// + /// The current filters for the subscription. + public TsCAeSubscriptionFilters GetFilters() + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // initialize arguments. + int pdwEventType; + int pdwNumCategories; + IntPtr ppidEventCategories; + int pdwLowSeverity; + int pdwHighSeverity; + int pdwNumAreas; + IntPtr ppsAreaList; + int pdwNumSources; + IntPtr ppsSourceList; + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).GetFilter( + out pdwEventType, + out pdwNumCategories, + out ppidEventCategories, + out pdwLowSeverity, + out pdwHighSeverity, + out pdwNumAreas, + out ppsAreaList, + out pdwNumSources, + out ppsSourceList); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetFilter", e); + } + + // marshal results + var categoryIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppidEventCategories, pdwNumCategories, true); + var areaIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppsAreaList, pdwNumAreas, true); + var sourceIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetUnicodeStrings(ref ppsSourceList, pdwNumSources, true); + + // build results. + var filters = new TsCAeSubscriptionFilters + { + EventTypes = pdwEventType, LowSeverity = pdwLowSeverity, HighSeverity = pdwHighSeverity + }; + + + filters.Categories.AddRange(categoryIDs); + filters.Areas.AddRange(areaIDs); + filters.Sources.AddRange(sourceIDs); + + // return results. + return filters; + } + } + + /// + /// Sets the current filters for the subscription. + /// + /// The new filters to use for the subscription. + public void SetFilters(TsCAeSubscriptionFilters filters) + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).SetFilter( + filters.EventTypes, + filters.Categories.Count, + filters.Categories.ToArray(), + filters.LowSeverity, + filters.HighSeverity, + filters.Areas.Count, + filters.Areas.ToArray(), + filters.Sources.Count, + filters.Sources.ToArray()); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SetFilter", e); + } + } + } + + //====================================================================== + // Attribute Management + + /// + /// Returns the set of attributes to return with event notifications. + /// + /// The set of attributes to returned with event notifications. + public int[] GetReturnedAttributes(int eventCategory) + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // initialize arguments. + int pdwCount; + IntPtr ppidAttributeIDs; + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).GetReturnedAttributes( + eventCategory, + out pdwCount, + out ppidAttributeIDs); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.GetReturnedAttributes", e); + } + + // marshal results + var attributeIDs = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref ppidAttributeIDs, pdwCount, true); + + // return results. + return attributeIDs; + } + } + + /// + /// Selects the set of attributes to return with event notifications. + /// + /// The specific event category for which the attributes apply. + /// The list of attribute ids to return. + public void SelectReturnedAttributes(int eventCategory, int[] attributeIDs) + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).SelectReturnedAttributes( + eventCategory, + attributeIDs?.Length ?? 0, + attributeIDs ?? Array.Empty()); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.SelectReturnedAttributes", e); + } + } + } + + //====================================================================== + // Refresh + + /// + /// Force a refresh for all active conditions and inactive, unacknowledged conditions whose event notifications match the filter of the event subscription. + /// + public void Refresh() + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).Refresh(0); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.Refresh", e); + } + } + } + + /// + /// Cancels an outstanding refresh request. + /// + public void CancelRefresh() + { + lock (this) + { + // verify state and arguments. + if (subscription_ == null) throw new NotConnectedException(); + + // invoke COM method. + try + { + ((IOPCEventSubscriptionMgt)subscription_).CancelRefresh(0); + } + catch (Exception e) + { + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCEventSubscriptionMgt.CancelRefresh", e); + } + } + } + #endregion + + #region IOPCEventSink Members + /// + /// A class that implements the IOPCEventSink interface. + /// + private class Callback : IOPCEventSink + { + /// + /// Initializes the object with the containing subscription object. + /// + public Callback(object clientHandle) + { + clientHandle_ = clientHandle; + } + + /// + /// Raised when data changed callbacks arrive. + /// + public event TsCAeDataChangedEventHandler DataChangedEvent + { + add { lock (this) { DataChangedEventHandler += value; } } + remove { lock (this) { DataChangedEventHandler -= value; } } + } + + /// + /// Called when a data changed event is received. + /// + public void OnEvent( + int hClientSubscription, + int bRefresh, + int bLastRefresh, + int dwCount, + ONEVENTSTRUCT[] pEvents) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions, true); + try + { + lock (this) + { + // do nothing if no connections. + if (DataChangedEventHandler == null) return; + + // un marshal item values. + var notifications = Interop.GetEventNotifications(pEvents); + + foreach (var notification in notifications) + { + notification.ClientHandle = clientHandle_; + } + + if (!LicenseHandler.IsExpired) + { + // invoke the callback. + DataChangedEventHandler?.Invoke(notifications, bRefresh != 0, bLastRefresh != 0); + } + } + } + catch (Exception e) + { + Utils.Trace(e, "Exception '{0}' in event handler.", e.Message); + } + } + + #region Private Members + private object clientHandle_; + private event TsCAeDataChangedEventHandler DataChangedEventHandler; + #endregion + } + #endregion + + #region Private Methods + + /// + /// Establishes a connection point callback with the COM server. + /// + private void Advise() + { + if (connection_ == null) + { + connection_ = new ConnectionPoint(subscription_, typeof(IOPCEventSink).GUID); + connection_.Advise(callback_); + } + } + + /// + /// Closes a connection point callback with the COM server. + /// + private void Unadvise() + { + if (connection_ != null) + { + if (connection_.Unadvise() == 0) + { + connection_.Dispose(); + connection_ = null; + } + } + } + #endregion + + #region Private Members + private object subscription_; + private object clientHandle_; + private bool supportsAe11_ = true; + private ConnectionPoint connection_; + private Callback callback_; + + /// + /// The synchronization object for subscription access + /// + private static volatile object lock_ = new object(); + + private bool disposed_; + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/ApplicationInstance.cs b/Technosoftware/DaAeHdaClient.Com/ApplicationInstance.cs new file mode 100644 index 0000000..16aa88d --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/ApplicationInstance.cs @@ -0,0 +1,97 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// Manages the license to enable the different product versions. + /// + public partial class ApplicationInstance + { + #region Nested Enums + /// + /// The possible authentication levels. + /// + [Flags] + public enum AuthenticationLevel : uint + { + /// + /// Tells DCOM to choose the authentication level using its normal security blanket negotiation algorithm. + /// + Default = 0, + + /// + /// Performs no authentication. + /// + None = 1, + + /// + /// Authenticates the credentials of the client only when the client establishes a relationship with the server. Datagram transports always use Packet instead. + /// + Connect = 2, + + /// + /// Authenticates only at the beginning of each remote procedure call when the server receives the request. Datagram transports use Packet instead. + /// + Call = 3, + + /// + /// Authenticates that all data received is from the expected client. + /// + Packet = 4, + + /// + /// Authenticates and verifies that none of the data transferred between client and server has been modified. + /// + Integrity = 5, + + /// + /// Authenticates all previous levels and encrypts the argument value of each remote procedure call. + /// + Privacy = 6, + } + #endregion + + #region Public Methods + /// + /// Initializes COM security. This should be called directly at the beginning of an application and can only be called once. + /// + /// The default authentication level for the process. Both servers and clients use this parameter when they call CoInitializeSecurity. With the Windows Update KB5004442 a higher authentication level of Integrity must be used. + public static void InitializeSecurity(AuthenticationLevel authenticationLevel) + { + if (!InitializeSecurityCalled) + { + Com.Interop.InitializeSecurity((uint)authenticationLevel); + InitializeSecurityCalled = true; + } + } + #endregion + + #region Internal Fields + internal static bool InitializeSecurityCalled; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/ComUtils.cs b/Technosoftware/DaAeHdaClient.Com/ComUtils.cs new file mode 100644 index 0000000..0606b1a --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/ComUtils.cs @@ -0,0 +1,2792 @@ +#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.Reflection; +using System.Net; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +using Technosoftware.DaAeHdaClient.Utilities; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// Exposes WIN32 and COM API functions. + /// + internal class ComUtils + { + #region NetApi Function Declarations + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct SERVER_INFO_100 + { + public uint sv100_platform_id; + [MarshalAs(UnmanagedType.LPWStr)] + public string sv100_name; + } + + private const uint LEVEL_SERVER_INFO_100 = 100; + private const uint LEVEL_SERVER_INFO_101 = 101; + + private const int MAX_PREFERRED_LENGTH = -1; + + private const uint SV_TYPE_WORKSTATION = 0x00000001; + private const uint SV_TYPE_SERVER = 0x00000002; + + [DllImport("Netapi32.dll")] + private static extern int NetServerEnum( + IntPtr servername, + uint level, + out IntPtr bufptr, + int prefmaxlen, + out int entriesread, + out int totalentries, + uint servertype, + IntPtr domain, + IntPtr resume_handle); + + [DllImport("Netapi32.dll")] + private static extern int NetApiBufferFree(IntPtr buffer); + + /// + /// Enumerates computers on the local network. + /// + public static List EnumComputers() + { + IntPtr pInfo; + int totalEntries; + + int entriesRead; + var result = NetServerEnum( + IntPtr.Zero, + LEVEL_SERVER_INFO_100, + out pInfo, + MAX_PREFERRED_LENGTH, + out entriesRead, + out totalEntries, + SV_TYPE_WORKSTATION | SV_TYPE_SERVER, + IntPtr.Zero, + IntPtr.Zero); + + if (result != 0) + { + throw new ApplicationException("NetApi Error = " + string.Format("0x{0:X8}", result)); + } + + var computers = new List(); + + var pos = pInfo; + + for (var ii = 0; ii < entriesRead; ii++) + { + var info = (SERVER_INFO_100)Marshal.PtrToStructure(pos, typeof(SERVER_INFO_100)); + + computers.Add(info.sv100_name); + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(SERVER_INFO_100))); + } + + NetApiBufferFree(pInfo); + + return computers; + } + #endregion + + #region OLE32 Function/Interface Declarations + private const int MAX_MESSAGE_LENGTH = 1024; + + private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + [DllImport("Kernel32.dll")] + private static extern int FormatMessageW( + int dwFlags, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + IntPtr lpBuffer, + int nSize, + IntPtr Arguments); + + [DllImport("Kernel32.dll")] + private static extern int GetSystemDefaultLangID(); + + [DllImport("Kernel32.dll")] + private static extern int GetUserDefaultLangID(); + + /// + /// The WIN32 system default locale. + /// + public const int LOCALE_SYSTEM_DEFAULT = 0x800; + + /// + /// The WIN32 user default locale. + /// + public const int LOCALE_USER_DEFAULT = 0x400; + + /// + /// The base for the WIN32 FILETIME structure. + /// + private static readonly DateTime FILETIME_BaseTime = new DateTime(1601, 1, 1); + + /// + /// WIN32 GUID struct declaration. + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct GUID + { + public int Data1; + public short Data2; + public short Data3; + [MarshalAs(UnmanagedType.ByValArray, SizeConst=8)] + public byte[] Data4; + } + + /// + /// The size, in bytes, of a VARIANT structure. + /// + private const int VARIANT_SIZE = 0x10; + + [DllImport("OleAut32.dll")] + private static extern int VariantChangeTypeEx( + IntPtr pvargDest, + IntPtr pvarSrc, + int lcid, + ushort wFlags, + short vt); + + /// + /// Intializes a pointer to a VARIANT. + /// + [DllImport("oleaut32.dll")] + private static extern void VariantInit(IntPtr pVariant); + + /// + /// Frees all memory referenced by a VARIANT stored in unmanaged memory. + /// + [DllImport("oleaut32.dll")] + public static extern void VariantClear(IntPtr pVariant); + + private const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005 + private const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A + + private const int VARIANT_NOVALUEPROP = 0x01; + private const int VARIANT_ALPHABOOL = 0x02; // For VT_BOOL to VT_BSTR conversions convert to "True"/"False" instead of + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct SOLE_AUTHENTICATION_SERVICE + { + public uint dwAuthnSvc; + public uint dwAuthzSvc; + [MarshalAs(UnmanagedType.LPWStr)] + public string pPrincipalName; + public int hr; + } + + private const uint RPC_C_AUTHN_NONE = 0; + private const uint RPC_C_AUTHN_DCE_PRIVATE = 1; + private const uint RPC_C_AUTHN_DCE_PUBLIC = 2; + private const uint RPC_C_AUTHN_DEC_PUBLIC = 4; + private const uint RPC_C_AUTHN_GSS_NEGOTIATE = 9; + private const uint RPC_C_AUTHN_WINNT = 10; + private const uint RPC_C_AUTHN_GSS_SCHANNEL = 14; + private const uint RPC_C_AUTHN_GSS_KERBEROS = 16; + private const uint RPC_C_AUTHN_DPA = 17; + private const uint RPC_C_AUTHN_MSN = 18; + private const uint RPC_C_AUTHN_DIGEST = 21; + private const uint RPC_C_AUTHN_MQ = 100; + private const uint RPC_C_AUTHN_DEFAULT = 0xFFFFFFFF; + + private const uint RPC_C_AUTHZ_NONE = 0; + private const uint RPC_C_AUTHZ_NAME = 1; + private const uint RPC_C_AUTHZ_DCE = 2; + private const uint RPC_C_AUTHZ_DEFAULT = 0xffffffff; + + private const uint RPC_C_AUTHN_LEVEL_DEFAULT = 0; + private const uint RPC_C_AUTHN_LEVEL_NONE = 1; + private const uint RPC_C_AUTHN_LEVEL_CONNECT = 2; + private const uint RPC_C_AUTHN_LEVEL_CALL = 3; + private const uint RPC_C_AUTHN_LEVEL_PKT = 4; + private const uint RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5; + private const uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; + + private const uint RPC_C_IMP_LEVEL_ANONYMOUS = 1; + private const uint RPC_C_IMP_LEVEL_IDENTIFY = 2; + private const uint RPC_C_IMP_LEVEL_IMPERSONATE = 3; + private const uint RPC_C_IMP_LEVEL_DELEGATE = 4; + + private const uint EOAC_NONE = 0x00; + private const uint EOAC_MUTUAL_AUTH = 0x01; + private const uint EOAC_CLOAKING = 0x10; + private const uint EOAC_STATIC_CLOAKING = 0x20; + private const uint EOAC_DYNAMIC_CLOAKING = 0x40; + private const uint EOAC_SECURE_REFS = 0x02; + private const uint EOAC_ACCESS_CONTROL = 0x04; + private const uint EOAC_APPID = 0x08; + + + /// If function succeeds, it returns 0(S_OK). Otherwise, it returns an error code. + [DllImport("ole32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] + private static extern int CoInitializeEx( + [In, Optional] IntPtr pvReserved, + [In] COINIT dwCoInit //DWORD + ); + + [DllImport("ole32.dll")] + private static extern int CoInitializeSecurity( + IntPtr pSecDesc, + int cAuthSvc, + SOLE_AUTHENTICATION_SERVICE[] asAuthSvc, + IntPtr pReserved1, + uint dwAuthnLevel, + uint dwImpLevel, + IntPtr pAuthList, + uint dwCapabilities, + IntPtr pReserved3); + + [DllImport("ole32.dll")] + private static extern int CoQueryProxyBlanket( + [MarshalAs(UnmanagedType.IUnknown)] + object pProxy, + ref uint pAuthnSvc, + ref uint pAuthzSvc, + [MarshalAs(UnmanagedType.LPWStr)] + ref string pServerPrincName, + ref uint pAuthnLevel, + ref uint pImpLevel, + ref IntPtr pAuthInfo, + ref uint pCapabilities); + + [DllImport("ole32.dll")] + private static extern int CoSetProxyBlanket( + [MarshalAs(UnmanagedType.IUnknown)] + object pProxy, + uint pAuthnSvc, + uint pAuthzSvc, + IntPtr pServerPrincName, + uint pAuthnLevel, + uint pImpLevel, + IntPtr pAuthInfo, + uint pCapabilities); + + private static readonly IntPtr COLE_DEFAULT_PRINCIPAL = new IntPtr(-1); + private static readonly IntPtr COLE_DEFAULT_AUTHINFO = new IntPtr(-1); + + + private enum COINIT : uint //tagCOINIT + { + COINIT_MULTITHREADED = 0x0, //Initializes the thread for multi-threaded object concurrency. + COINIT_APARTMENTTHREADED = 0x2, //Initializes the thread for apartment-threaded object concurrency + COINIT_DISABLE_OLE1DDE = 0x4, //Disables DDE for OLE1 support + COINIT_SPEED_OVER_MEMORY = 0x8, //Trade memory for speed + } + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct COSERVERINFO + { + public uint dwReserved1; + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszName; + public IntPtr pAuthInfo; + public uint dwReserved2; + }; + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct COAUTHINFO + { + public uint dwAuthnSvc; + public uint dwAuthzSvc; + public IntPtr pwszServerPrincName; + public uint dwAuthnLevel; + public uint dwImpersonationLevel; + public IntPtr pAuthIdentityData; + public uint dwCapabilities; + } + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct COAUTHIDENTITY + { + public IntPtr User; + public uint UserLength; + public IntPtr Domain; + public uint DomainLength; + public IntPtr Password; + public uint PasswordLength; + public uint Flags; + } + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct MULTI_QI + { + public IntPtr iid; + [MarshalAs(UnmanagedType.IUnknown)] + public object pItf; + public uint hr; + } + + private const uint CLSCTX_INPROC_SERVER = 0x1; + private const uint CLSCTX_INPROC_HANDLER = 0x2; + private const uint CLSCTX_LOCAL_SERVER = 0x4; + private const uint CLSCTX_REMOTE_SERVER = 0x10; + private const uint CLSCTX_DISABLE_AAA = 0x8000; + + private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); + + private const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1; + private const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2; + + [DllImport("ole32.dll")] + private static extern void CoCreateInstanceEx( + ref Guid clsid, + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + uint dwClsCtx, + [In] + ref COSERVERINFO pServerInfo, + uint dwCount, + [In, Out] + MULTI_QI[] pResults); + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] + private struct LICINFO + { + public int cbLicInfo; + [MarshalAs(UnmanagedType.Bool)] + public bool fRuntimeKeyAvail; + [MarshalAs(UnmanagedType.Bool)] + public bool fLicVerified; + } + + [ComImport] + [GuidAttribute("00000001-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClassFactory + { + void CreateInstance( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.Interface)] + [Out] out object ppvObject); + + void LockServer( + [MarshalAs(UnmanagedType.Bool)] + bool fLock); + } + + [ComImport] + [GuidAttribute("B196B28F-BAB4-101A-B69C-00AA00341D07")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClassFactory2 + { + void CreateInstance( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.Interface)] + [Out] out object ppvObject); + + void LockServer( + [MarshalAs(UnmanagedType.Bool)] + bool fLock); + + void GetLicInfo( + [In,Out] ref LICINFO pLicInfo); + + void RequestLicKey( + int dwReserved, + [MarshalAs(UnmanagedType.BStr)] + string pbstrKey); + + void CreateInstanceLic( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.IUnknown)] + object punkReserved, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.BStr)] + string bstrKey, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppvObject); + } + + [DllImport("ole32.dll")] + private static extern void CoGetClassObject( + [MarshalAs(UnmanagedType.LPStruct)] + Guid clsid, + uint dwClsContext, + [In] ref COSERVERINFO pServerInfo, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppv); + + private const int LOGON32_PROVIDER_DEFAULT = 0; + private const int LOGON32_LOGON_INTERACTIVE = 2; + private const int LOGON32_LOGON_NETWORK = 3; + + private const int SECURITY_ANONYMOUS = 0; + private const int SECURITY_IDENTIFICATION = 1; + private const int SECURITY_IMPERSONATION = 2; + private const int SECURITY_DELEGATION = 3; + + [DllImport("advapi32.dll", SetLastError=true)] + private static extern bool LogonUser( + string lpszUsername, + string lpszDomain, + string lpszPassword, + int dwLogonType, + int dwLogonProvider, + ref IntPtr phToken); + + [DllImport("kernel32.dll", CharSet=CharSet.Auto)] + private extern static bool CloseHandle(IntPtr handle); + + [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] + private extern static bool DuplicateToken( + IntPtr ExistingTokenHandle, + int SECURITY_IMPERSONATION_LEVEL, + ref IntPtr DuplicateTokenHandle); + #endregion + + #region ServerInfo Class + /// + /// A class used to allocate and deallocate the elements of a COSERVERINFO structure. + /// + class ServerInfo + { + #region internal interface + /// + /// Allocates a COSERVERINFO structure. + /// + public COSERVERINFO Allocate(string hostName, OpcUserIdentity identity) + { + // initialize server info structure. + var serverInfo = new COSERVERINFO(); + + serverInfo.pwszName = hostName; + serverInfo.pAuthInfo = IntPtr.Zero; + serverInfo.dwReserved1 = 0; + serverInfo.dwReserved2 = 0; + + // no authentication for default identity + if (OpcUserIdentity.IsDefault(identity)) + { + return serverInfo; + } + + m_hUserName = GCHandle.Alloc(identity.Username, GCHandleType.Pinned); + m_hPassword = GCHandle.Alloc(identity.Password, GCHandleType.Pinned); + m_hDomain = GCHandle.Alloc(identity.Domain, GCHandleType.Pinned); + + m_hIdentity = new GCHandle(); + + // create identity structure. + var authIdentity = new COAUTHIDENTITY(); + + authIdentity.User = m_hUserName.AddrOfPinnedObject(); + authIdentity.UserLength = (uint)((identity.Username != null)?identity.Username.Length:0); + authIdentity.Password = m_hPassword.AddrOfPinnedObject(); + authIdentity.PasswordLength = (uint)((identity.Password != null)?identity.Password.Length:0); + authIdentity.Domain = m_hDomain.AddrOfPinnedObject(); + authIdentity.DomainLength = (uint)((identity.Domain != null)?identity.Domain.Length:0); + authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + m_hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); + + // create authorization info structure. + var authInfo = new COAUTHINFO(); + + authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; + authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; + authInfo.pwszServerPrincName = IntPtr.Zero; + authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; + authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + authInfo.pAuthIdentityData = m_hIdentity.AddrOfPinnedObject(); + authInfo.dwCapabilities = EOAC_NONE; // EOAC_DYNAMIC_CLOAKING; + + m_hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned); + + // update server info structure. + serverInfo.pAuthInfo = m_hAuthInfo.AddrOfPinnedObject(); + + return serverInfo; + } + + /// + /// Deallocated memory allocated when the COSERVERINFO structure was created. + /// + public void Deallocate() + { + if (m_hUserName.IsAllocated) m_hUserName.Free(); + if (m_hPassword.IsAllocated) m_hPassword.Free(); + if (m_hDomain.IsAllocated) m_hDomain.Free(); + if (m_hIdentity.IsAllocated) m_hIdentity.Free(); + if (m_hAuthInfo.IsAllocated) m_hAuthInfo.Free(); + } + #endregion + + #region Private Members + private GCHandle m_hUserName; + private GCHandle m_hPassword; + private GCHandle m_hDomain; + private GCHandle m_hIdentity; + private GCHandle m_hAuthInfo; + #endregion + } + #endregion + + #region Initialization Functions + /// + /// Initializes COM security. + /// + public static void InitializeSecurity() + { + var error = CoInitializeSecurity( + IntPtr.Zero, + -1, + null, + IntPtr.Zero, + RPC_C_AUTHN_LEVEL_CONNECT, + RPC_C_IMP_LEVEL_IMPERSONATE, + IntPtr.Zero, + EOAC_DYNAMIC_CLOAKING, + IntPtr.Zero); + + // this call will fail in the debugger if the + // 'Debug | Enable Visual Studio Hosting Process' + // option is checked in the project properties. + if (error != 0) + { + // throw new ExternalException("CoInitializeSecurity: " + GetSystemMessage(error), error); + } + } + + /// + /// Determines if the host is the local host. + /// + private static bool IsLocalHost(string hostName) + { + // lookup requested host. + var requestedHost = Dns.GetHostEntry(hostName); + + if (requestedHost == null || requestedHost.AddressList == null) + { + return true; + } + + // check for loopback. + for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) + { + var requestedIP = requestedHost.AddressList[ii]; + + if (requestedIP == null || requestedIP.Equals(IPAddress.Loopback)) + { + return true; + } + } + + // lookup local host. + var localHost = Dns.GetHostEntry(Dns.GetHostName()); + + if (localHost == null || localHost.AddressList == null) + { + return false; + } + + // check for localhost. + for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) + { + var requestedIP = requestedHost.AddressList[ii]; + + for (var jj = 0; jj < localHost.AddressList.Length; jj++) + { + if (requestedIP.Equals(localHost.AddressList[jj])) + { + return true; + } + } + } + + // must be remote. + return false; + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstance(Guid clsid, string hostName, OpcUserIdentity identity) + { + return CreateInstance1(clsid, hostName, identity); + } + + /// + /// Creates an instance of a COM server. + /// + public static object CreateInstance1(Guid clsid, string hostName, OpcUserIdentity identity) + { + var serverInfo = new ServerInfo(); + var coserverInfo = serverInfo.Allocate(hostName, identity); + + var hIID = GCHandle.Alloc(IID_IUnknown, GCHandleType.Pinned); + + var results = new MULTI_QI[1]; + + results[0].iid = hIID.AddrOfPinnedObject(); + results[0].pItf = null; + results[0].hr = 0; + + try + { + // check whether connecting locally or remotely. + var clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; + + if (!string.IsNullOrEmpty(hostName) && hostName != "localhost") + { + clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + } + + // create an instance. + CoCreateInstanceEx( + ref clsid, + null, + clsctx, + ref coserverInfo, + 1, + results); + } + finally + { + if (hIID.IsAllocated) hIID.Free(); + serverInfo.Deallocate(); + } + + if (results[0].hr != 0) + { + throw new OpcResultException(new OpcResult(OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Could not create COM server '{0}' on host '{1}'. Reason: {2}.", clsid, hostName, GetSystemMessage((int)results[0].hr, LOCALE_SYSTEM_DEFAULT))); + } + + return results[0].pItf; + } + + // COM impersonation is a nice feature but variations between behavoirs on different + // windows platforms make it virtually impossible to support. This code is left here + // in case it becomes a critical requirement in the future. + #if COM_IMPERSONATION_SUPPORT + /// + /// Returns the WindowsIdentity associated with a UserIdentity. + /// + public static WindowsPrincipal GetPrincipalFromUserIdentity(UserIdentity user) + { + if (UserIdentity.IsDefault(user)) + { + return null; + } + + // validate the credentials. + IntPtr token = IntPtr.Zero; + + bool result = LogonUser( + user.Username, + user.Domain, + user.Password, + LOGON32_LOGON_NETWORK, + LOGON32_PROVIDER_DEFAULT, + ref token); + + if (!result) + { + throw new OpcResultException(new OpcResult((int)OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null), String.Format("Could not logon as user '{0}'. Reason: {1}.", user.Username, GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT))); + + throw OpcResultException.Create( + StatusCodes.BadIdentityTokenRejected, + "Could not logon as user '{0}'. Reason: {1}.", + user.Username, + GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT)); + } + + try + { + // create the windows identity. + WindowsIdentity identity = new WindowsIdentity(token); + + // validate the identity. + identity.Impersonate(); + + // return a principal. + return new WindowsPrincipal(identity); + } + finally + { + CloseHandle(token); + } + } + + /// + /// Sets the security settings for the proxy. + /// + public static void SetProxySecurity(object server, UserIdentity user) + { + // allocate the + GCHandle hUserName = GCHandle.Alloc(user.Username, GCHandleType.Pinned); + GCHandle hPassword = GCHandle.Alloc(user.Password, GCHandleType.Pinned); + GCHandle hDomain = GCHandle.Alloc(user.Domain, GCHandleType.Pinned); + + GCHandle hIdentity = new GCHandle(); + + // create identity structure. + COAUTHIDENTITY authIdentity = new COAUTHIDENTITY(); + + authIdentity.User = hUserName.AddrOfPinnedObject(); + authIdentity.UserLength = (uint)((user.Username != null) ? user.Username.Length : 0); + authIdentity.Password = hPassword.AddrOfPinnedObject(); + authIdentity.PasswordLength = (uint)((user.Password != null) ? user.Password.Length : 0); + authIdentity.Domain = hDomain.AddrOfPinnedObject(); + authIdentity.DomainLength = (uint)((user.Domain != null) ? user.Domain.Length : 0); + authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); + + try + { + SetProxySecurity(server, hIdentity.AddrOfPinnedObject()); + } + finally + { + hUserName.Free(); + hPassword.Free(); + hDomain.Free(); + hIdentity.Free(); + } + } + + /// + /// Sets the security settings for the proxy. + /// + public static void SetProxySecurity(object server, IntPtr pAuthInfo) + { + // get the existing proxy settings. + uint pAuthnSvc = 0; + uint pAuthzSvc = 0; + string pServerPrincName = ""; + uint pAuthnLevel = 0; + uint pImpLevel = 0; + IntPtr pAuthInfo2 = IntPtr.Zero; + uint pCapabilities = 0; + + CoQueryProxyBlanket( + server, + ref pAuthnSvc, + ref pAuthzSvc, + ref pServerPrincName, + ref pAuthnLevel, + ref pImpLevel, + ref pAuthInfo2, + ref pCapabilities); + + pAuthnSvc = RPC_C_AUTHN_WINNT; + pAuthzSvc = RPC_C_AUTHZ_NONE; + pAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; + pImpLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + pCapabilities = EOAC_DYNAMIC_CLOAKING; + + // update proxy security settings. + CoSetProxyBlanket( + server, + pAuthnSvc, + pAuthzSvc, + COLE_DEFAULT_PRINCIPAL, + pAuthnLevel, + pImpLevel, + pAuthInfo, + pCapabilities); + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstance2(Guid clsid, string hostName, UserIdentity identity) + { + // validate the host name before proceeding (exception thrown if host is not valid). + bool isLocalHost = IsLocalHost(hostName); + + // allocate the connection info. + ServerInfo serverInfo = new ServerInfo(); + COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); + object instance = null; + IClassFactory factory = null; + + try + { + // create the factory. + object server = null; + + CoGetClassObject( + clsid, + (isLocalHost)?CLSCTX_LOCAL_SERVER:CLSCTX_REMOTE_SERVER, + ref coserverInfo, + IID_IUnknown, + out server); + + // SetProxySecurity(server, coserverInfo.pAuthInfo); + + factory = (IClassFactory)server; + + // check for valid factory. + if (factory == null) + { + throw OpcResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory for COM server '{0}' on host '{1}'.", clsid, hostName); + } + + // SetProxySecurity(factory, coserverInfo.pAuthInfo); + + factory.CreateInstance(null, IID_IUnknown, out instance); + + // SetProxySecurity(instance, coserverInfo.pAuthInfo); + } + finally + { + serverInfo.Deallocate(); + } + + return instance; + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstanceWithLicenseKey(Guid clsid, string hostName, UserIdentity identity, string licenseKey) + { + ServerInfo serverInfo = new ServerInfo(); + COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); + object instance = null; + IClassFactory2 factory = null; + + try + { + // check whether connecting locally or remotely. + uint clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; + + if (hostName != null && hostName.Length > 0) + { + clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + } + + // get the class factory. + object server = null; + + CoGetClassObject( + clsid, + clsctx, + ref coserverInfo, + typeof(IClassFactory2).GUID, + out server); + + // SetProxySecurity(server, coserverInfo.pAuthInfo); + + factory = (IClassFactory2)server; + + // check for valid factory. + if (factory == null) + { + throw OpcResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory2 for COM server '{0}' on host '{1}'.", clsid, hostName); + } + + // SetProxySecurity(factory, coserverInfo.pAuthInfo); + + // create instance. + factory.CreateInstanceLic( + null, + null, + IID_IUnknown, + licenseKey, + out instance); + + // SetProxySecurity(instance, coserverInfo.pAuthInfo); + } + finally + { + serverInfo.Deallocate(); + ComUtils.ReleaseServer(factory); + } + + return instance; + } + #endif + #endregion + + + #region Conversion Functions +#if TEST + + /// + /// Tests if the specified string matches the specified pattern. + /// + public static bool Match(string target, string pattern, bool caseSensitive) + { + // an empty pattern always matches. + if (pattern == null || pattern.Length == 0) + { + return true; + } + + // an empty string never matches. + if (target == null || target.Length == 0) + { + return false; + } + + // check for exact match + if (caseSensitive) + { + if (target == pattern) + { + return true; + } + } + else + { + if (target.ToLower() == pattern.ToLower()) + { + return true; + } + } + + char c; + char p; + char l; + + int pIndex = 0; + int tIndex = 0; + + while (tIndex < target.Length && pIndex < pattern.Length) + { + p = ConvertCase(pattern[pIndex++], caseSensitive); + + if (pIndex > pattern.Length) + { + return (tIndex >= target.Length); // if end of string true + } + + switch (p) + { + // match zero or more char. + case '*': + { + while (pIndex < pattern.Length && pattern[pIndex] == '*') + { + pIndex++; + } + + while (tIndex < target.Length) + { + if (Match(target.Substring(tIndex++), pattern.Substring(pIndex), caseSensitive)) + { + return true; + } + } + + return Match(target, pattern.Substring(pIndex), caseSensitive); + } + + // match any one char. + case '?': + { + // check if end of string when looking for a single character. + if (tIndex >= target.Length) + { + return false; + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length-1) + { + return false; + } + + tIndex++; + break; + } + + // match char set + case '[': + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (tIndex > target.Length) + { + return false; // syntax + } + + l = '\0'; + + // match a char if NOT in set [] + if (pattern[pIndex] == '!') + { + ++pIndex; + + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then + { + break; // no match found + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + return false; // if in range, return false + } + } + + l = p; + + if (c == p) // if char matches this element + { + return false; // return false + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + } + + // match if char is in set [] + else + { + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then no match found + { + return false; + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + break; // if in range, move on + } + } + + l = p; + + if (c == p) // if char matches this element move on + { + break; + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + + while (pIndex < pattern.Length && p != ']') // got a match in char set skip to end of set + { + p = pattern[pIndex++]; + } + } + + break; + } + + // match digit. + case '#': + { + c = target[tIndex++]; + + if (!Char.IsDigit(c)) + { + return false; // not a digit + } + + break; + } + + // match exact char. + default: + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (c != p) // check for exact char + { + return false; // not a match + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length-1) + { + return false; + } + + break; + } + } + } + + return true; + } + + // ConvertCase + private static char ConvertCase(char c, bool caseSensitive) + { + return (caseSensitive)?c:Char.ToUpper(c); + } + + /// + /// Unmarshals and frees an array of HRESULTs. + /// + public static int[] GetStatusCodes(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + // unmarshal HRESULT array. + int[] output = new int[size]; + Marshal.Copy(pArray, output, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return output; + } + + /// + /// Unmarshals and frees an array of 32 bit integers. + /// + public static int[] GetInt32s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + int[] array = new int[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Unmarshals and frees an array of 32 bit integers. + /// + public static int[] GetUInt32s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + int[] array = new int[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + + /// + /// Allocates and marshals an array of 32 bit integers. + /// + public static IntPtr GetInt32s(int[] input) + { + IntPtr output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Int32))*input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Unmarshals and frees a array of 16 bit integers. + /// + public static short[] GetInt16s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + short[] array = new short[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Allocates and marshals an array of 16 bit integers. + /// + public static IntPtr GetInt16s(short[] input) + { + IntPtr output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Int16))*input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Marshals an array of strings into a unmanaged memory buffer + /// + /// The array of strings to marshal + /// The pointer to the unmanaged memory buffer + public static IntPtr GetUnicodeStrings(string[] values) + { + int size = (values != null)?values.Length:0; + + if (size <= 0) + { + return IntPtr.Zero; + } + + IntPtr pValues = IntPtr.Zero; + + int[] pointers = new int[size]; + + for (int ii = 0; ii < size; ii++) + { + pointers[ii] = (int)Marshal.StringToCoTaskMemUni(values[ii]); + } + + pValues = Marshal.AllocCoTaskMem(values.Length*Marshal.SizeOf(typeof(IntPtr))); + Marshal.Copy(pointers, 0, pValues, size); + + return pValues; + } + + /// + /// Unmarshals and frees a array of unicode strings. + /// + public static string[] GetUnicodeStrings(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + IntPtr[] pointers = new IntPtr[size]; + Marshal.Copy(pArray, pointers, 0, size); + + string[] strings = new string[size]; + + for (int ii = 0; ii < size; ii++) + { + IntPtr pString = pointers[ii]; + strings[ii] = Marshal.PtrToStringUni(pString); + if (deallocate) Marshal.FreeCoTaskMem(pString); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return strings; + } + + /// + /// Marshals a DateTime as a WIN32 FILETIME. + /// + /// The DateTime object to marshal + /// The WIN32 FILETIME + public static System.Runtime.InteropServices.ComTypes.FILETIME GetFILETIME(DateTime datetime) + { + System.Runtime.InteropServices.ComTypes.FILETIME filetime; + + if (datetime <= FILETIME_BaseTime) + { + filetime.dwHighDateTime = 0; + filetime.dwLowDateTime = 0; + return filetime; + } + + // adjust for WIN32 FILETIME base. + long ticks = 0; + ticks = datetime.Subtract(new TimeSpan(FILETIME_BaseTime.Ticks)).Ticks; + + filetime.dwHighDateTime = (int)((ticks>>32) & 0xFFFFFFFF); + filetime.dwLowDateTime = (int)(ticks & 0xFFFFFFFF); + + return filetime; + } + + /// + /// Unmarshals a WIN32 FILETIME from a pointer. + /// + /// A pointer to a FILETIME structure. + /// A DateTime object. + public static DateTime GetDateTime(IntPtr pFiletime) + { + if (pFiletime == IntPtr.Zero) + { + return DateTime.MinValue; + } + + return GetDateTime((System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(pFiletime, typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + /// + /// Unmarshals a WIN32 FILETIME. + /// + public static DateTime GetDateTime(System.Runtime.InteropServices.ComTypes.FILETIME filetime) + { + // check for invalid value. + if (filetime.dwHighDateTime < 0) + { + return DateTime.MinValue; + } + + // convert FILETIME structure to a 64 bit integer. + long buffer = (long)filetime.dwHighDateTime; + + if (buffer < 0) + { + buffer += ((long)UInt32.MaxValue+1); + } + + long ticks = (buffer<<32); + + buffer = (long)filetime.dwLowDateTime; + + if (buffer < 0) + { + buffer += ((long)UInt32.MaxValue+1); + } + + ticks += buffer; + + // check for invalid value. + if (ticks == 0) + { + return DateTime.MinValue; + } + + // adjust for WIN32 FILETIME base. + return FILETIME_BaseTime.Add(new TimeSpan(ticks)); + } + + /// + /// Marshals an array of DateTimes into an unmanaged array of FILETIMEs + /// + /// The array of DateTimes to marshal + /// The IntPtr array of FILETIMEs + public static IntPtr GetFILETIMEs(DateTime[] datetimes) + { + int count = (datetimes != null)?datetimes.Length:0; + + if (count <= 0) + { + return IntPtr.Zero; + } + + IntPtr pFiletimes = Marshal.AllocCoTaskMem(count*Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + + IntPtr pos = pFiletimes; + + for (int ii = 0; ii < count; ii++) + { + Marshal.StructureToPtr(GetFILETIME(datetimes[ii]), pos, false); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + return pFiletimes; + } + + /// + /// Unmarshals an array of WIN32 FILETIMEs as DateTimes. + /// + public static DateTime[] GetDateTimes(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + DateTime[] datetimes = new DateTime[size]; + + IntPtr pos = pArray; + + for (int ii = 0; ii < size; ii++) + { + datetimes[ii] = GetDateTime(pos); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return datetimes; + } + + /// + /// Unmarshals an array of WIN32 GUIDs as Guid. + /// + public static Guid[] GetGUIDs(ref IntPtr pInput, int size, bool deallocate) + { + if (pInput == IntPtr.Zero || size <= 0) + { + return null; + } + + Guid[] guids = new Guid[size]; + + IntPtr pos = pInput; + + for (int ii = 0; ii < size; ii++) + { + GUID input = (GUID)Marshal.PtrToStructure(pInput, typeof(GUID)); + + guids[ii] = new Guid(input.Data1, input.Data2, input.Data3, input.Data4); + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(GUID))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + + return guids; + } + + /// + /// Converts an object into a value that can be marshalled to a VARIANT. + /// + /// The object to convert. + /// The converted object. + public static object GetVARIANT(object source) + { + // check for invalid args. + if (source == null) + { + return null; + } + + return GetVARIANT(source, TypeInfo.Construct(source)); + } + + /// + /// Converts an object into a value that can be marshalled to a VARIANT. + /// + /// The object to convert. + /// The converted object. + public static object GetVARIANT(Variant source) + { + return GetVARIANT(source.Value, source.TypeInfo); + } + + /// + /// Converts an object into a value that can be marshalled to a VARIANT. + /// + /// The object to convert. + /// The type info. + /// The converted object. + public static object GetVARIANT(object source, TypeInfo typeInfo) + { + // check for invalid args. + if (source == null) + { + return null; + } + + try + { + switch (typeInfo.BuiltInType) + { + case BuiltInType.Boolean: + case BuiltInType.Byte: + case BuiltInType.Int16: + case BuiltInType.UInt16: + case BuiltInType.Int32: + case BuiltInType.UInt32: + case BuiltInType.Int64: + case BuiltInType.UInt64: + case BuiltInType.Float: + case BuiltInType.Double: + case BuiltInType.String: + case BuiltInType.DateTime: + { + return source; + } + + case BuiltInType.ByteString: + { + if (typeInfo.ValueRank < 0) + { + return source; + } + + return VariantToObjectArray((Array)source); + } + + case BuiltInType.Guid: + case BuiltInType.LocalizedText: + case BuiltInType.QualifiedName: + case BuiltInType.NodeId: + case BuiltInType.ExpandedNodeId: + case BuiltInType.XmlElement: + { + return TypeInfo.Cast(source, typeInfo, BuiltInType.String); + } + + case BuiltInType.StatusCode: + { + return TypeInfo.Cast(source, typeInfo, BuiltInType.UInt32); + } + + case BuiltInType.Variant: + { + if (typeInfo.ValueRank < 0) + { + return GetVARIANT(((Variant)source).Value); + } + + return VariantToObjectArray((Array)source); + } + + case BuiltInType.ExtensionObject: + { + if (typeInfo.ValueRank < 0) + { + byte[] body = null; + + ExtensionObject extension = (ExtensionObject)source; + + switch (extension.Encoding) + { + case ExtensionObjectEncoding.Binary: + { + body = (byte[])extension.Body; + break; + } + + case ExtensionObjectEncoding.Xml: + { + body = new UTF8Encoding().GetBytes(((XmlElement)extension.Body).OuterXml); + break; + } + + case ExtensionObjectEncoding.EncodeableObject: + { + BinaryEncoder encoder = new BinaryEncoder(ServiceMessageContext.GlobalContext); + encoder.WriteEncodeable(null, (IEncodeable)extension.Body, null); + body = encoder.CloseAndReturnBuffer(); + break; + } + } + + return body; + } + + return VariantToObjectArray((Array)source); + } + + case BuiltInType.DataValue: + case BuiltInType.DiagnosticInfo: + { + return "(unsupported)"; + } + } + } + catch (Exception e) + { + return e.Message; + } + + // no conversion required. + return source; + } + + /// + /// Converts a Variant array to an Object array. + /// + private static Array VariantToObjectArray(Array input) + { + int[] dimensions = new int[input.Rank]; + + for (int ii = 0; ii < dimensions.Length; ii++) + { + dimensions[ii] = input.GetLength(ii); + } + + Array output = Array.CreateInstance(typeof(object), dimensions); + + int length = output.Length; + int[] indexes = new int[dimensions.Length]; + + for (int ii = 0; ii < length; ii++) + { + int divisor = output.Length; + + for (int jj = 0; jj < indexes.Length; jj++) + { + divisor /= dimensions[jj]; + indexes[jj] = (ii/divisor)%dimensions[jj]; + } + + object value = input.GetValue(indexes); + output.SetValue(GetVARIANT(value), indexes); + } + + return output; + } + + /// + /// Marshals an array objects into an unmanaged array of VARIANTs. + /// + /// An array of the objects to be marshalled + /// Whether the objects should have troublesome types removed before marhalling. + /// An pointer to the array in unmanaged memory + public static IntPtr GetVARIANTs(object[] values, bool preprocess) + { + int count = (values != null)?values.Length:0; + + if (count <= 0) + { + return IntPtr.Zero; + } + + IntPtr pValues = Marshal.AllocCoTaskMem(count*VARIANT_SIZE); + + IntPtr pos = pValues; + + for (int ii = 0; ii < count; ii++) + { + if (preprocess) + { + Marshal.GetNativeVariantForObject(GetVARIANT(values[ii]), pos); + } + else + { + Marshal.GetNativeVariantForObject(values[ii], pos); + } + + pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE); + } + + return pValues; + } + + /// + /// Unmarshals an array of VARIANTs as objects. + /// + public static object[] GetVARIANTs(ref IntPtr pArray, int size, bool deallocate) + { + // this method unmarshals VARIANTs one at a time because a single bad value throws + // an exception with GetObjectsForNativeVariants(). This approach simply sets the + // offending value to null. + + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + object[] values = new object[size]; + + IntPtr pos = pArray; + + for (int ii = 0; ii < size; ii++) + { + try + { + values[ii] = Marshal.GetObjectForNativeVariant(pos); + if (deallocate) VariantClear(pos); + } + catch (Exception) + { + values[ii] = null; + } + + pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return values; + } + + /// + /// Converts a LCID to a Locale string. + /// + public static string GetLocale(int input) + { + try + { + if (input == LOCALE_SYSTEM_DEFAULT || input == LOCALE_USER_DEFAULT || input == 0) + { + return CultureInfo.InvariantCulture.Name; + } + + return new CultureInfo(input).Name; + } + catch (Exception e) + { + throw new OpcResultException(StatusCodes.Bad, "Unrecognized locale provided.", e); + } + } + + /// + /// Converts a Locale string to a LCID. + /// + public static int GetLocale(string input) + { + // check for the default culture. + if (input == null || input == "") + { + return 0; + } + + CultureInfo locale = null; + + try { locale = new CultureInfo(input); } + catch { locale = CultureInfo.CurrentCulture; } + + return locale.LCID; + } + + /// + /// Converts the VARTYPE to a UA Data Type ID. + /// + public static NodeId GetDataTypeId(short input) + { + switch ((VarEnum)Enum.ToObject(typeof(VarEnum), (input & ~(short)VarEnum.VT_ARRAY))) + { + case VarEnum.VT_I1: return DataTypes.SByte; + case VarEnum.VT_UI1: return DataTypes.Byte; + case VarEnum.VT_I2: return DataTypes.Int16; + case VarEnum.VT_UI2: return DataTypes.UInt16; + case VarEnum.VT_I4: return DataTypes.Int32; + case VarEnum.VT_UI4: return DataTypes.UInt32; + case VarEnum.VT_I8: return DataTypes.Int64; + case VarEnum.VT_UI8: return DataTypes.UInt64; + case VarEnum.VT_R4: return DataTypes.Float; + case VarEnum.VT_R8: return DataTypes.Double; + case VarEnum.VT_BOOL: return DataTypes.Boolean; + case VarEnum.VT_DATE: return DataTypes.DateTime; + case VarEnum.VT_BSTR: return DataTypes.String; + case VarEnum.VT_CY: return DataTypes.String; + case VarEnum.VT_EMPTY: return DataTypes.BaseDataType; + + case VarEnum.VT_VARIANT: + { + return DataTypes.BaseDataType; + } + } + + return NodeId.Null; + } + + /// + /// Converts the VARTYPE to a UA ValueRank. + /// + public static int GetValueRank(short input) + { + if ((((short)VarEnum.VT_ARRAY & input) != 0)) + { + return ValueRanks.OneDimension; + } + + return ValueRanks.Scalar; + } + + /// + /// Converts the VARTYPE to a UA Data Type ID. + /// + public static NodeId GetDataTypeId(short input, out bool isArray) + { + isArray = (((short)VarEnum.VT_ARRAY & input) != 0); + return GetDataTypeId(input); + } + + /// + /// Converts the VARTYPE to a SystemType + /// + public static Type GetSystemType(short input) + { + bool isArray = (((short)VarEnum.VT_ARRAY & input) != 0); + VarEnum varType = (VarEnum)Enum.ToObject(typeof(VarEnum), (input & ~(short)VarEnum.VT_ARRAY)); + + if (!isArray) + { + switch (varType) + { + case VarEnum.VT_I1: return typeof(sbyte); + case VarEnum.VT_UI1: return typeof(byte); + case VarEnum.VT_I2: return typeof(short); + case VarEnum.VT_UI2: return typeof(ushort); + case VarEnum.VT_I4: return typeof(int); + case VarEnum.VT_UI4: return typeof(uint); + case VarEnum.VT_I8: return typeof(long); + case VarEnum.VT_UI8: return typeof(ulong); + case VarEnum.VT_R4: return typeof(float); + case VarEnum.VT_R8: return typeof(double); + case VarEnum.VT_BOOL: return typeof(bool); + case VarEnum.VT_DATE: return typeof(DateTime); + case VarEnum.VT_BSTR: return typeof(string); + case VarEnum.VT_CY: return typeof(decimal); + case VarEnum.VT_EMPTY: return typeof(object); + case VarEnum.VT_VARIANT: return typeof(object); + } + } + else + { + switch (varType) + { + case VarEnum.VT_I1: return typeof(sbyte[]); + case VarEnum.VT_UI1: return typeof(byte[]); + case VarEnum.VT_I2: return typeof(short[]); + case VarEnum.VT_UI2: return typeof(ushort[]); + case VarEnum.VT_I4: return typeof(int[]); + case VarEnum.VT_UI4: return typeof(uint[]); + case VarEnum.VT_I8: return typeof(long[]); + case VarEnum.VT_UI8: return typeof(ulong[]); + case VarEnum.VT_R4: return typeof(float[]); + case VarEnum.VT_R8: return typeof(double[]); + case VarEnum.VT_BOOL: return typeof(bool[]); + case VarEnum.VT_DATE: return typeof(DateTime[]); + case VarEnum.VT_BSTR: return typeof(string[]); + case VarEnum.VT_CY: return typeof(decimal[]); + case VarEnum.VT_EMPTY: return typeof(object[]); + case VarEnum.VT_VARIANT: return typeof(object[]); + } + } + + return null; + } + + /// + /// Returns the VARTYPE for the value. + /// + public static VarEnum GetVarType(object input) + { + if (input == null) + { + return VarEnum.VT_EMPTY; + } + + return GetVarType(input.GetType()); + } + + /// + /// Converts the system type to a VARTYPE. + /// + public static VarEnum GetVarType(System.Type type) + { + if (type == null) + { + return VarEnum.VT_EMPTY; + } + + if (type == null) return VarEnum.VT_EMPTY; + if (type == typeof(sbyte)) return VarEnum.VT_I1; + if (type == typeof(byte)) return VarEnum.VT_UI1; + if (type == typeof(short)) return VarEnum.VT_I2; + if (type == typeof(ushort)) return VarEnum.VT_UI2; + if (type == typeof(int)) return VarEnum.VT_I4; + if (type == typeof(uint)) return VarEnum.VT_UI4; + if (type == typeof(long)) return VarEnum.VT_I8; + if (type == typeof(ulong)) return VarEnum.VT_UI8; + if (type == typeof(float)) return VarEnum.VT_R4; + if (type == typeof(double)) return VarEnum.VT_R8; + if (type == typeof(decimal)) return VarEnum.VT_CY; + if (type == typeof(bool)) return VarEnum.VT_BOOL; + if (type == typeof(DateTime)) return VarEnum.VT_DATE; + if (type == typeof(string)) return VarEnum.VT_BSTR; + if (type == typeof(sbyte[])) return VarEnum.VT_ARRAY | VarEnum.VT_I1; + if (type == typeof(byte[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI1; + if (type == typeof(short[])) return VarEnum.VT_ARRAY | VarEnum.VT_I2; + if (type == typeof(ushort[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI2; + if (type == typeof(int[])) return VarEnum.VT_ARRAY | VarEnum.VT_I4; + if (type == typeof(uint[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI4; + if (type == typeof(long[])) return VarEnum.VT_ARRAY | VarEnum.VT_I8; + if (type == typeof(ulong[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI8; + if (type == typeof(float[])) return VarEnum.VT_ARRAY | VarEnum.VT_R4; + if (type == typeof(double[])) return VarEnum.VT_ARRAY | VarEnum.VT_R8; + if (type == typeof(decimal[])) return VarEnum.VT_ARRAY | VarEnum.VT_CY; + if (type == typeof(bool[])) return VarEnum.VT_ARRAY | VarEnum.VT_BOOL; + if (type == typeof(DateTime[])) return VarEnum.VT_ARRAY | VarEnum.VT_DATE; + if (type == typeof(string[])) return VarEnum.VT_ARRAY | VarEnum.VT_BSTR; + if (type == typeof(object[])) return VarEnum.VT_ARRAY | VarEnum.VT_VARIANT; + + return VarEnum.VT_EMPTY; + } + + /// + /// Converts the TypeInfo to a VARTYPE. + /// + public static VarEnum GetVarType(TypeInfo typeInfo) + { + if (typeInfo == null) + { + return VarEnum.VT_EMPTY; + } + + VarEnum vtType = VarEnum.VT_EMPTY; + + switch (typeInfo.BuiltInType) + { + case BuiltInType.Boolean: { vtType = VarEnum.VT_BOOL; break; } + case BuiltInType.SByte: { vtType = VarEnum.VT_I1; break; } + case BuiltInType.Byte: { vtType = VarEnum.VT_UI1; break; } + case BuiltInType.Int16: { vtType = VarEnum.VT_I2; break; } + case BuiltInType.UInt16: { vtType = VarEnum.VT_UI2; break; } + case BuiltInType.Int32: { vtType = VarEnum.VT_I4; break; } + case BuiltInType.UInt32: { vtType = VarEnum.VT_UI4; break; } + case BuiltInType.Int64: { vtType = VarEnum.VT_I8; break; } + case BuiltInType.UInt64: { vtType = VarEnum.VT_UI8; break; } + case BuiltInType.Float: { vtType = VarEnum.VT_R4; break; } + case BuiltInType.Double: { vtType = VarEnum.VT_R8; break; } + case BuiltInType.String: { vtType = VarEnum.VT_BSTR; break; } + case BuiltInType.DateTime: { vtType = VarEnum.VT_DATE; break; } + case BuiltInType.Guid: { vtType = VarEnum.VT_BSTR; break; } + case BuiltInType.ByteString: { vtType = VarEnum.VT_ARRAY | VarEnum.VT_UI1; break; } + case BuiltInType.XmlElement: { vtType = VarEnum.VT_BSTR; break; } + case BuiltInType.NodeId: { vtType = VarEnum.VT_BSTR; break; } + case BuiltInType.ExpandedNodeId: { vtType = VarEnum.VT_BSTR; break; } + case BuiltInType.QualifiedName: { vtType = VarEnum.VT_BSTR; break; } + case BuiltInType.LocalizedText: { vtType = VarEnum.VT_BSTR; break; } + case BuiltInType.StatusCode: { vtType = VarEnum.VT_UI4; break; } + case BuiltInType.ExtensionObject: { vtType = VarEnum.VT_ARRAY | VarEnum.VT_UI1; break; } + case BuiltInType.Enumeration: { vtType = VarEnum.VT_I4; break; } + case BuiltInType.Number: { vtType = VarEnum.VT_R8; break; } + case BuiltInType.Integer: { vtType = VarEnum.VT_I8; break; } + case BuiltInType.UInteger: { vtType = VarEnum.VT_UI8; break; } + + case BuiltInType.Variant: + { + if (typeInfo.ValueRank == ValueRanks.Scalar) + { + return VarEnum.VT_EMPTY; + } + + vtType = VarEnum.VT_VARIANT; + break; + } + + default: + { + return VarEnum.VT_EMPTY; + } + } + + if (typeInfo.ValueRank > 0) + { + vtType |= VarEnum.VT_ARRAY; + } + + return vtType; + } + + /// + /// Converts a value to the specified type using COM conversion rules. + /// + public static int ChangeTypeForCOM(object source, VarEnum targetType, out object target) + { + return ChangeTypeForCOM(source, GetVarType(source), targetType, out target); + } + + /// + /// Converts a value to the specified type using COM conversion rules. + /// + public static int ChangeTypeForCOM(object source, VarEnum sourceType, VarEnum targetType, out object target) + { + target = source; + + // check for trivial case. + if (sourceType == targetType) + { + return ResultIds.S_OK; + } + + // check for conversions to date time from string. + string stringValue = source as string; + + if (stringValue != null && targetType == VarEnum.VT_DATE) + { + try + { + target = System.Convert.ToDateTime(stringValue); + return ResultIds.S_OK; + } + catch + { + target = null; + return ResultIds.DISP_E_OVERFLOW; + } + } + + // check for conversions from date time to boolean. + if (sourceType == VarEnum.VT_DATE && targetType == VarEnum.VT_BOOL) + { + target = !(new DateTime(1899, 12, 30, 0, 0, 0).Equals((DateTime)source)); + return ResultIds.S_OK; + } + + // check for conversions from float to double. + if (sourceType == VarEnum.VT_R4 && targetType == VarEnum.VT_R8) + { + target = System.Convert.ToDouble((float)source); + return ResultIds.S_OK; + } + + // check for array conversion. + Array array = source as Array; + bool targetIsArray = ((targetType & VarEnum.VT_ARRAY) != 0); + + if (array != null && targetIsArray) + { + VarEnum elementType = (VarEnum)((short)targetType & ~(short)VarEnum.VT_ARRAY); + + Array convertedArray = Array.CreateInstance(GetSystemType((short)elementType), array.Length); + + for (int ii = 0; ii < array.Length; ii++) + { + object elementValue = null; + int error = ChangeTypeForCOM(array.GetValue(ii), elementType, out elementValue); + + if (error < 0) + { + target = null; + return ResultIds.DISP_E_OVERFLOW; + } + + convertedArray.SetValue(elementValue, ii); + } + + target = convertedArray; + return ResultIds.S_OK; + } + else if (array == null && !targetIsArray) + { + IntPtr pvargDest = Marshal.AllocCoTaskMem(16); + IntPtr pvarSrc = Marshal.AllocCoTaskMem(16); + + VariantInit(pvargDest); + VariantInit(pvarSrc); + + Marshal.GetNativeVariantForObject(source, pvarSrc); + + try + { + // change type. + int error = VariantChangeTypeEx( + pvargDest, + pvarSrc, + Thread.CurrentThread.CurrentCulture.LCID, + VARIANT_NOVALUEPROP | VARIANT_ALPHABOOL, + (short)targetType); + + // check error code. + if (error != 0) + { + target = null; + return error; + } + + // unmarshal result. + object result = Marshal.GetObjectForNativeVariant(pvargDest); + + // check for invalid unsigned <=> signed conversions. + switch (targetType) + { + case VarEnum.VT_I1: + case VarEnum.VT_I2: + case VarEnum.VT_I4: + case VarEnum.VT_I8: + case VarEnum.VT_UI1: + case VarEnum.VT_UI2: + case VarEnum.VT_UI4: + case VarEnum.VT_UI8: + { + // ignore issue for conversions from boolean. + if (sourceType == VarEnum.VT_BOOL) + { + break; + } + + decimal sourceAsDecimal = 0; + decimal resultAsDecimal = System.Convert.ToDecimal(result); + + try { sourceAsDecimal = System.Convert.ToDecimal(source); } + catch { sourceAsDecimal = 0; } + + if ((sourceAsDecimal < 0 && resultAsDecimal > 0) || (sourceAsDecimal > 0 && resultAsDecimal < 0)) + { + target = null; + return ResultIds.E_RANGE; + } + + // conversion from datetime should have failed. + if (sourceType == VarEnum.VT_DATE) + { + if (resultAsDecimal == 0) + { + target = null; + return ResultIds.E_RANGE; + } + } + + break; + } + + case VarEnum.VT_R8: + { + // fix precision problem introduced with conversion from float to double. + if (sourceType == VarEnum.VT_R4) + { + result = System.Convert.ToDouble(source.ToString()); + } + + break; + } + } + + target = result; + return ResultIds.S_OK; + } + finally + { + VariantClear(pvargDest); + VariantClear(pvarSrc); + + Marshal.FreeCoTaskMem(pvargDest); + Marshal.FreeCoTaskMem(pvarSrc); + } + } + else if (array != null && targetType == VarEnum.VT_BSTR) + { + int count = ((Array)source).Length; + + StringBuilder buffer = new StringBuilder(); + + buffer.Append("{"); + + foreach (object element in (Array)source) + { + object elementValue = null; + + int error = ChangeTypeForCOM(element, VarEnum.VT_BSTR, out elementValue); + + if (error < 0) + { + target = null; + return error; + } + + buffer.Append((string)elementValue); + + count--; + + if (count > 0) + { + buffer.Append(" | "); + } + } + + buffer.Append("}"); + + target = buffer.ToString(); + return ResultIds.S_OK; + } + + // no conversions between scalar and array types allowed. + target = null; + return ResultIds.E_BADTYPE; + } + + /// + /// Returns true is the object is a valid COM type. + /// + public static bool IsValidComType(object input) + { + Array array = input as Array; + + if (array != null) + { + foreach (object value in array) + { + if (!IsValidComType(value)) + { + return false; + } + } + + return true; + } + + return GetVarType(input) != VarEnum.VT_EMPTY; + } + + /// + /// Converts a COM value to something that UA clients can deal with. + /// + public static object ProcessComValue(object value) + { + // flatten any multi-dimensional array. + Array array = value as Array; + + if (array != null) + { + if (array.Rank > 1) + { + value = array = Utils.FlattenArray(array); + } + + // convert array of decimal to strings. + if (array != null && array.GetType().GetElementType() == typeof(decimal)) + { + string[] clone = new string[array.Length]; + + for (int ii = 0; ii < array.Length; ii++) + { + clone[ii] = Convert.ToString(array.GetValue(ii)); + } + + value = clone; + } + } + + // convert scalar decimal to a string. + if (value is decimal) + { + value = Convert.ToString(value); + } + + return value; + } + + /// + /// Converts a DA quality code to a status code. + /// + public static StatusCode GetQualityCode(short quality) + { + StatusCode code = 0; + + // convert quality status. + switch ((short)(quality & 0x00FC)) + { + case OpcRcw.Da.Qualities.OPC_QUALITY_GOOD: { code = StatusCodes.Good; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_LOCAL_OVERRIDE: { code = StatusCodes.GoodLocalOverride; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN: { code = StatusCodes.Uncertain; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_SUB_NORMAL: { code = StatusCodes.UncertainSubNormal; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_CAL: { code = StatusCodes.UncertainSensorNotAccurate; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_EGU_EXCEEDED: { code = StatusCodes.UncertainEngineeringUnitsExceeded; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_LAST_USABLE: { code = StatusCodes.UncertainLastUsableValue; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_BAD: { code = StatusCodes.Bad; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_CONFIG_ERROR: { code = StatusCodes.BadConfigurationError; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_NOT_CONNECTED: { code = StatusCodes.BadNotConnected; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_COMM_FAILURE: { code = StatusCodes.BadNoCommunication; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_DEVICE_FAILURE: { code = StatusCodes.BadDeviceFailure; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_FAILURE: { code = StatusCodes.BadSensorFailure; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_LAST_KNOWN: { code = StatusCodes.BadOutOfService; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_OUT_OF_SERVICE: { code = StatusCodes.BadOutOfService; break; } + case OpcRcw.Da.Qualities.OPC_QUALITY_WAITING_FOR_INITIAL_DATA: { code = StatusCodes.BadWaitingForInitialData; break; } + } + + // convert the limit status. + switch ((short)(quality & 0x0003)) + { + case OpcRcw.Da.Qualities.OPC_LIMIT_LOW: { code.LimitBits = LimitBits.Low; break; } + case OpcRcw.Da.Qualities.OPC_LIMIT_HIGH: { code.LimitBits = LimitBits.High; break; } + case OpcRcw.Da.Qualities.OPC_LIMIT_CONST: { code.LimitBits = LimitBits.Constant; break; } + } + + // return the combined code. + return code; + } + + /// + /// Converts a UA status code to a DA quality code. + /// + public static short GetQualityCode(StatusCode input) + { + short code = 0; + + // convert quality status. + switch (input.CodeBits) + { + case StatusCodes.Good: { code = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD; break; } + case StatusCodes.GoodLocalOverride: { code = OpcRcw.Da.Qualities.OPC_QUALITY_LOCAL_OVERRIDE; break; } + case StatusCodes.Uncertain: { code = OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN; break; } + case StatusCodes.UncertainSubNormal: { code = OpcRcw.Da.Qualities.OPC_QUALITY_SUB_NORMAL; break; } + case StatusCodes.UncertainSensorNotAccurate: { code = OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_CAL; break; } + case StatusCodes.UncertainEngineeringUnitsExceeded: { code = OpcRcw.Da.Qualities.OPC_QUALITY_EGU_EXCEEDED; break; } + case StatusCodes.UncertainLastUsableValue: { code = OpcRcw.Da.Qualities.OPC_QUALITY_LAST_USABLE; break; } + case StatusCodes.Bad: { code = OpcRcw.Da.Qualities.OPC_QUALITY_BAD; break; } + case StatusCodes.BadConfigurationError: { code = OpcRcw.Da.Qualities.OPC_QUALITY_CONFIG_ERROR; break; } + case StatusCodes.BadNotConnected: { code = OpcRcw.Da.Qualities.OPC_QUALITY_NOT_CONNECTED; break; } + case StatusCodes.BadNoCommunication: { code = OpcRcw.Da.Qualities.OPC_QUALITY_COMM_FAILURE; break; } + case StatusCodes.BadOutOfService: { code = OpcRcw.Da.Qualities.OPC_QUALITY_OUT_OF_SERVICE; break; } + case StatusCodes.BadDeviceFailure: { code = OpcRcw.Da.Qualities.OPC_QUALITY_DEVICE_FAILURE; break; } + case StatusCodes.BadSensorFailure: { code = OpcRcw.Da.Qualities.OPC_QUALITY_SENSOR_FAILURE; break; } + case StatusCodes.BadWaitingForInitialData: { code = OpcRcw.Da.Qualities.OPC_QUALITY_WAITING_FOR_INITIAL_DATA; break; } + + default: + { + if (StatusCode.IsBad(input)) + { + code = OpcRcw.Da.Qualities.OPC_QUALITY_BAD; + break; + } + + if (StatusCode.IsUncertain(input)) + { + code = OpcRcw.Da.Qualities.OPC_QUALITY_UNCERTAIN; + break; + } + + code = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD; + break; + } + } + + // convert the limit status. + switch (input.LimitBits) + { + case LimitBits.Low: { code |= OpcRcw.Da.Qualities.OPC_LIMIT_LOW; break; } + case LimitBits.High: { code |= OpcRcw.Da.Qualities.OPC_LIMIT_HIGH; break; } + case LimitBits.Constant: { code |= OpcRcw.Da.Qualities.OPC_LIMIT_CONST; break; } + } + + // return the combined code. + return code; + } + + /// + /// Converts a HDA quality code to a StatusCode. + /// + public static StatusCode GetHdaQualityCode(uint quality) + { + uint hdaCode = quality & 0xFFFF0000; + + // check for bits indicating an out right error. + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_NOBOUND) != 0) + { + return StatusCodes.BadBoundNotFound; + } + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_NODATA) != 0) + { + return StatusCodes.BadNoData; + } + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_DATALOST) != 0) + { + return StatusCodes.BadDataLost; + } + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_CONVERSION) != 0) + { + return StatusCodes.BadTypeMismatch; + } + + // Get DA part (lower 2 bytes). + StatusCode code = GetQualityCode((short)(quality & 0x0000FFFF)); + + // check for bits that are placed in the info bits. + AggregateBits aggregateBits = 0; + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_EXTRADATA) != 0) + { + aggregateBits |= AggregateBits.ExtraData; + } + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_INTERPOLATED) != 0) + { + aggregateBits |= AggregateBits.Interpolated; + } + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_RAW) != 0) + { + aggregateBits |= AggregateBits.Raw; + } + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_CALCULATED) != 0) + { + aggregateBits |= AggregateBits.Calculated; + } + + if ((hdaCode & OpcRcw.Hda.Constants.OPCHDA_PARTIAL) != 0) + { + aggregateBits |= AggregateBits.Partial; + } + + return code.SetAggregateBits(aggregateBits); + } + + /// + /// Converts a UA status code to a HDA quality code. + /// + public static uint GetHdaQualityCode(StatusCode input) + { + // check for fatal errors. + switch (input.CodeBits) + { + case StatusCodes.BadBoundNotFound: { return OpcRcw.Hda.Constants.OPCHDA_NOBOUND; } + case StatusCodes.BadBoundNotSupported: { return OpcRcw.Hda.Constants.OPCHDA_NOBOUND; } + case StatusCodes.BadNoData: { return OpcRcw.Hda.Constants.OPCHDA_NODATA; } + case StatusCodes.BadDataLost: { return OpcRcw.Hda.Constants.OPCHDA_DATALOST; } + } + + // handle normal case. + uint code = Utils.ToUInt32(GetQualityCode(input)); + + // check for bits that are placed in the info bits. + AggregateBits aggregateBits = input.AggregateBits; + + if ((aggregateBits & AggregateBits.ExtraData) != 0) + { + code |= OpcRcw.Hda.Constants.OPCHDA_EXTRADATA; + } + + // set the source of the data. + if ((aggregateBits & AggregateBits.Interpolated) != 0) + { + code |= OpcRcw.Hda.Constants.OPCHDA_INTERPOLATED; + } + else if ((aggregateBits & AggregateBits.Calculated) != 0) + { + code |= OpcRcw.Hda.Constants.OPCHDA_CALCULATED; + } + else if ((aggregateBits & AggregateBits.Partial) != 0) + { + code |= OpcRcw.Hda.Constants.OPCHDA_PARTIAL; + } + else + { + code |= OpcRcw.Hda.Constants.OPCHDA_RAW; + } + + // return the combined code. + return code; + } +#endif + + /// + /// Returns the symbolic name for the specified error. + /// + public static string GetErrorText(Type type, int error) + { + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static); + + foreach (var field in fields) + { + if (error == (int)field.GetValue(type)) + { + return field.Name; + } + } + + return string.Format("0x{0:X8}", error); + } + + /// + /// Gets the error code for the exception. + /// + /// The exception. + /// The default code. + /// The error code + /// This method ignores the exception but makes it possible to keep track of ignored exceptions. + public static int GetErrorCode(Exception e, int defaultCode) + { + return defaultCode; + } + + /// + /// Releases the server if it is a true COM server. + /// + public static void ReleaseServer(object server) + { + if (server != null && server.GetType().IsCOMObject) + { + Marshal.ReleaseComObject(server); + } + } + + /// + /// Retrieves the system message text for the specified error. + /// + public static string GetSystemMessage(int error, int localeId) + { + int langId; + switch (localeId) + { + case LOCALE_SYSTEM_DEFAULT: + { + langId = GetSystemDefaultLangID(); + break; + } + + case LOCALE_USER_DEFAULT: + { + langId = GetUserDefaultLangID(); + break; + } + + default: + { + langId = (0xFFFF & localeId); + break; + } + } + + var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH); + + var result = FormatMessageW( + (int)FORMAT_MESSAGE_FROM_SYSTEM, + IntPtr.Zero, + error, + langId, + buffer, + MAX_MESSAGE_LENGTH-1, + IntPtr.Zero); + + if (result > 0) + { + var msg = Marshal.PtrToStringUni(buffer); + Marshal.FreeCoTaskMem(buffer); + + if (msg != null && msg.Length > 0) + { + return msg.Trim(); + } + } + + return string.Format("0x{0:X8}", error); + } + + /// + /// Converts an exception to an exception that returns a COM error code. + /// + public static Exception CreateComException(Exception e, int errorId) + { + return new COMException(e.Message, errorId); + } + + /// + /// Creates a COM exception. + /// + public static Exception CreateComException(string message, int errorId) + { + return new COMException(message, errorId); + } + + /// + /// Converts an exception to an exception that returns a COM error code. + /// + public static Exception CreateComException(int errorId) + { + return new COMException(string.Format("0x{0:X8}", errorId), errorId); + } + + /// + /// Converts an exception to an exception that returns a COM error code. + /// + public static Exception CreateComException(Exception e) + { + // nothing special required for external exceptions. + if (e is COMException) + { + return e; + } + + // convert other exceptions to E_FAIL. + return new COMException(e.Message, OpcResult.E_FAIL.Code); + } + + /// + /// Creates an error message for a failed COM function call. + /// + public static Exception CreateException(Exception e, string function) + { + return new OpcResultException(new OpcResult((int)OpcResult.E_NETWORK_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Call to {0} failed. Error: {1}.", function, GetSystemMessage(Marshal.GetHRForException(e), LOCALE_SYSTEM_DEFAULT))); + } + + /// + /// Checks if the error is an RPC error. + /// + public static bool IsRpcError(Exception e) + { + var error = Marshal.GetHRForException(e); + + // Assume that any 0x8007 is a fatal communication error. + // May need to update this check if the assumption proves to be incorrect. + if ((error & 0xFFFF0000) == 0x80070000) + { + error &= 0xFFFF; + + // check the RPC error range define in WinError.h + if (error >= 1700 && error < 1918) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the error for the exception is one of the recognized errors. + /// + public static bool IsUnknownError(Exception e, params int[] knownErrors) + { + var error = Marshal.GetHRForException(e); + + if (knownErrors != null) + { + for (var ii = 0; ii < knownErrors.Length; ii++) + { + if (knownErrors[ii] == error) + { + return false; + } + } + } + + return true; + } + #endregion + + #region Utility Functions + /// + /// Compares a string locale to a WIN32 localeId + /// + public static bool CompareLocales(int localeId, string locale, bool ignoreRegion) + { + // parse locale. + CultureInfo culture; + try + { + culture = new CultureInfo(locale); + } + catch (Exception) + { + return false; + } + + // only match the language portion of the locale id. + if (ignoreRegion) + { + if ((localeId & culture.LCID & 0x3FF) == (localeId & 0x3FF)) + { + return true; + } + } + + // check for exact match. + else + { + if (localeId == culture.LCID) + { + return true; + } + } + + return false; + } + + /// + /// Reports an unexpected exception during a COM operation. + /// + public static void TraceComError(Exception e, string format, params object[] args) + { + var message = Utils.Format(format, args); + + var code = Marshal.GetHRForException(e); + + var error = code.ToString(); + + if (error == null) + { + return; + } + } + #endregion + + + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/ConnectionPoint.cs b/Technosoftware/DaAeHdaClient.Com/ConnectionPoint.cs new file mode 100644 index 0000000..d8cd80c --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/ConnectionPoint.cs @@ -0,0 +1,107 @@ +#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 Technosoftware.OpcRcw.Comn; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// Adds and removes a connection point to a server. + /// + internal class ConnectionPoint : IDisposable + { + /// + /// The COM server that supports connection points. + /// + private IConnectionPoint server_; + + /// + /// The id assigned to the connection by the COM server. + /// + private int cookie_; + + /// + /// The number of times Advise() has been called without a matching Unadvise(). + /// + private int refs_; + + /// + /// Initializes the object by finding the specified connection point. + /// + public ConnectionPoint(object server, Guid iid) + { + ((IConnectionPointContainer)server).FindConnectionPoint(ref iid, out server_); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (server_ != null) + { + while (Unadvise() > 0) + { + } + try + { + Utilities.Interop.ReleaseServer(server_); + } + catch + { + // Ignore. COM Server probably no longer connected + } + + server_ = null; + } + } + + /// + /// The cookie returned in the advise call. + /// + public int Cookie => cookie_; + + //===================================================================== + // IConnectionPoint + + /// + /// Establishes a connection, if necessary and increments the reference count. + /// + public int Advise(object callback) + { + if (refs_++ == 0) server_.Advise(callback, out cookie_); + return refs_; + } + + /// + /// Decrements the reference count and closes the connection if no more references. + /// + public int Unadvise() + { + if (--refs_ == 0) server_.Unadvise(cookie_); + return refs_; + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Da/BrowsePosition.cs b/Technosoftware/DaAeHdaClient.Com/Da/BrowsePosition.cs new file mode 100644 index 0000000..3e83bbe --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Da/BrowsePosition.cs @@ -0,0 +1,60 @@ +#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 Technosoftware.DaAeHdaClient.Da; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Da +{ + /// + /// Implements an object that handles multi-step browse operations. + /// + [Serializable] + internal class BrowsePosition : TsCDaBrowsePosition + { + /// + /// The continuation point for a browse operation. + /// + internal string ContinuationPoint = null; + + /// + /// Indicates that elements that meet the filter criteria have not been returned. + /// + internal bool MoreElements = false; + + /// + /// Initializes a browse position + /// + internal BrowsePosition( + OpcItem itemID, + TsCDaBrowseFilters filters, + string continuationPoint) + : + base(itemID, filters) + { + ContinuationPoint = continuationPoint; + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Da/Interop.cs b/Technosoftware/DaAeHdaClient.Com/Da/Interop.cs new file mode 100644 index 0000000..1905949 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Da/Interop.cs @@ -0,0 +1,906 @@ +#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; +using System.Runtime.InteropServices; +using System.Reflection; + +using Technosoftware.DaAeHdaClient.Da; +#endregion + +#pragma warning disable 0618 + +namespace Technosoftware.DaAeHdaClient.Com.Da +{ + /// + /// Contains state information for a single asynchronous Technosoftware.DaAeHdaClient.Com.Da.Interop. + /// + internal class Interop + { + /// + /// Converts a standard FILETIME to an OpcRcw.Da.FILETIME structure. + /// + internal static OpcRcw.Da.FILETIME Convert(FILETIME input) + { + var output = new OpcRcw.Da.FILETIME(); + output.dwLowDateTime = input.dwLowDateTime; + output.dwHighDateTime = input.dwHighDateTime; + return output; + } + + /// + /// Converts an OpcRcw.Da.FILETIME to a standard FILETIME structure. + /// + internal static FILETIME Convert(OpcRcw.Da.FILETIME input) + { + var output = new FILETIME(); + output.dwLowDateTime = input.dwLowDateTime; + output.dwHighDateTime = input.dwHighDateTime; + return output; + } + + /// + /// Allocates and marshals a OPCSERVERSTATUS structure. + /// + internal static OpcRcw.Da.OPCSERVERSTATUS GetServerStatus(OpcServerStatus input, int groupCount) + { + var output = new OpcRcw.Da.OPCSERVERSTATUS(); + + if (input != null) + { + output.szVendorInfo = input.VendorInfo; + output.wMajorVersion = 0; + output.wMinorVersion = 0; + output.wBuildNumber = 0; + output.dwServerState = (OpcRcw.Da.OPCSERVERSTATE)input.ServerState; + output.ftStartTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.StartTime)); + output.ftCurrentTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.CurrentTime)); + output.ftLastUpdateTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.LastUpdateTime)); + output.dwBandWidth = -1; + output.dwGroupCount = groupCount; + output.wReserved = 0; + + if (input.ProductVersion != null) + { + var versions = input.ProductVersion.Split(new char[] { '.' }); + + if (versions.Length > 0) + { + try { output.wMajorVersion = System.Convert.ToInt16(versions[0]); } + catch { output.wMajorVersion = 0; } + } + + if (versions.Length > 1) + { + try { output.wMinorVersion = System.Convert.ToInt16(versions[1]); } + catch { output.wMinorVersion = 0; } + } + + output.wBuildNumber = 0; + + for (var ii = 2; ii < versions.Length; ii++) + { + try + { + output.wBuildNumber = (short)(output.wBuildNumber * 100 + System.Convert.ToInt16(versions[ii])); + } + catch + { + output.wBuildNumber = 0; + break; + } + } + } + } + + return output; + } + + /// + /// Unmarshals and deallocates a OPCSERVERSTATUS structure. + /// + internal static OpcServerStatus GetServerStatus(ref IntPtr pInput, bool deallocate) + { + OpcServerStatus output = null; + + if (pInput != IntPtr.Zero) + { + var status = (OpcRcw.Da.OPCSERVERSTATUS)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCSERVERSTATUS)); + + output = new OpcServerStatus(); + + output.VendorInfo = status.szVendorInfo; + output.ProductVersion = string.Format("{0}.{1}.{2}", status.wMajorVersion, status.wMinorVersion, status.wBuildNumber); + output.ServerState = (OpcServerState)status.dwServerState; + output.StatusInfo = null; + output.StartTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftStartTime)); + output.CurrentTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftCurrentTime)); + output.LastUpdateTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(status.ftLastUpdateTime)); + + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCSERVERSTATUS)); + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Converts a browseFilter values to the COM equivalent. + /// + internal static OpcRcw.Da.OPCBROWSEFILTER GetBrowseFilter(TsCDaBrowseFilter input) + { + switch (input) + { + case TsCDaBrowseFilter.All: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL; + case TsCDaBrowseFilter.Branch: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_BRANCHES; + case TsCDaBrowseFilter.Item: return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ITEMS; + } + + return OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL; + } + + /// + /// Converts a browseFilter values from the COM equivalent. + /// + internal static TsCDaBrowseFilter GetBrowseFilter(OpcRcw.Da.OPCBROWSEFILTER input) + { + switch (input) + { + case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ALL: return TsCDaBrowseFilter.All; + case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_BRANCHES: return TsCDaBrowseFilter.Branch; + case OpcRcw.Da.OPCBROWSEFILTER.OPC_BROWSE_FILTER_ITEMS: return TsCDaBrowseFilter.Item; + } + + return TsCDaBrowseFilter.All; + } + + /// + /// Allocates and marshals an array of HRESULT codes. + /// + internal static IntPtr GetHRESULTs(IOpcResult[] results) + { + // extract error codes from results. + var errors = new int[results.Length]; + + for (var ii = 0; ii < results.Length; ii++) + { + if (results[ii] != null) + { + errors[ii] = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(results[ii].Result); + } + else + { + errors[ii] = Result.E_INVALIDHANDLE; + } + } + + // marshal error codes. + var pErrors = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * results.Length); + Marshal.Copy(errors, 0, pErrors, results.Length); + + // return results. + return pErrors; + } + + /// + /// Unmarshals and deallocates an array of OPCBROWSEELEMENT structures. + /// + internal static TsCDaBrowseElement[] GetBrowseElements(ref IntPtr pInput, int count, bool deallocate) + { + TsCDaBrowseElement[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCDaBrowseElement[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + output[ii] = GetBrowseElement(pos, deallocate); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Allocates and marshals an array of OPCBROWSEELEMENT structures. + /// + internal static IntPtr GetBrowseElements(TsCDaBrowseElement[] input, bool propertiesRequested) + { + var output = IntPtr.Zero; + + if (input != null && input.Length > 0) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT)) * input.Length); + + var pos = output; + + for (var ii = 0; ii < input.Length; ii++) + { + var element = GetBrowseElement(input[ii], propertiesRequested); + Marshal.StructureToPtr(element, pos, false); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCBROWSEELEMENT))); + } + } + + return output; + } + + /// + /// Unmarshals and deallocates a OPCBROWSEELEMENT structures. + /// + internal static TsCDaBrowseElement GetBrowseElement(IntPtr pInput, bool deallocate) + { + TsCDaBrowseElement output = null; + + if (pInput != IntPtr.Zero) + { + var element = (OpcRcw.Da.OPCBROWSEELEMENT)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCBROWSEELEMENT)); + + output = new TsCDaBrowseElement(); + + output.Name = element.szName; + output.ItemPath = null; + output.ItemName = element.szItemID; + output.IsItem = ((element.dwFlagValue & OpcRcw.Da.Constants.OPC_BROWSE_ISITEM) != 0); + output.HasChildren = ((element.dwFlagValue & OpcRcw.Da.Constants.OPC_BROWSE_HASCHILDREN) != 0); + output.Properties = GetItemProperties(ref element.ItemProperties, deallocate); + + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCBROWSEELEMENT)); + } + } + + return output; + } + + /// + /// Allocates and marshals an OPCBROWSEELEMENT structure. + /// + internal static OpcRcw.Da.OPCBROWSEELEMENT GetBrowseElement(TsCDaBrowseElement input, bool propertiesRequested) + { + var output = new OpcRcw.Da.OPCBROWSEELEMENT(); + + if (input != null) + { + output.szName = input.Name; + output.szItemID = input.ItemName; + output.dwFlagValue = 0; + output.ItemProperties = GetItemProperties(input.Properties); + + if (input.IsItem) + { + output.dwFlagValue |= OpcRcw.Da.Constants.OPC_BROWSE_ISITEM; + } + + if (input.HasChildren) + { + output.dwFlagValue |= OpcRcw.Da.Constants.OPC_BROWSE_HASCHILDREN; + } + } + + return output; + } + + /// + /// Creates an array of property codes. + /// + internal static int[] GetPropertyIDs(TsDaPropertyID[] propertyIDs) + { + var output = new ArrayList(); + + if (propertyIDs != null) + { + foreach (var propertyID in propertyIDs) + { + output.Add(propertyID.Code); + } + } + + return (int[])output.ToArray(typeof(int)); + } + + /// + /// Creates an array of property codes. + /// + internal static TsDaPropertyID[] GetPropertyIDs(int[] propertyIDs) + { + var output = new ArrayList(); + + if (propertyIDs != null) + { + foreach (var propertyID in propertyIDs) + { + output.Add(GetPropertyID(propertyID)); + } + } + + return (TsDaPropertyID[])output.ToArray(typeof(TsDaPropertyID)); + } + + /// + /// Unmarshals and deallocates an array of OPCITEMPROPERTIES structures. + /// + internal static TsCDaItemPropertyCollection[] GetItemPropertyCollections(ref IntPtr pInput, int count, bool deallocate) + { + TsCDaItemPropertyCollection[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCDaItemPropertyCollection[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + var list = (OpcRcw.Da.OPCITEMPROPERTIES)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMPROPERTIES)); + + output[ii] = new TsCDaItemPropertyCollection(); + output[ii].ItemPath = null; + output[ii].ItemName = null; + output[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(list.hrErrorID); + + var properties = GetItemProperties(ref list, deallocate); + + if (properties != null) + { + output[ii].AddRange(properties); + } + + if (deallocate) + { + Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMPROPERTIES)); + } + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Allocates and marshals an array of OPCITEMPROPERTIES structures. + /// + internal static IntPtr GetItemPropertyCollections(TsCDaItemPropertyCollection[] input) + { + var output = IntPtr.Zero; + + if (input != null && input.Length > 0) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES)) * input.Length); + + var pos = output; + + for (var ii = 0; ii < input.Length; ii++) + { + var properties = new OpcRcw.Da.OPCITEMPROPERTIES(); + + if (input[ii].Count > 0) + { + properties = GetItemProperties((TsCDaItemProperty[])input[ii].ToArray(typeof(TsCDaItemProperty))); + } + + properties.hrErrorID = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input[ii].Result); + + Marshal.StructureToPtr(properties, pos, false); + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTIES))); + } + } + + return output; + } + + /// + /// Unmarshals and deallocates a OPCITEMPROPERTIES structures. + /// + internal static TsCDaItemProperty[] GetItemProperties(ref OpcRcw.Da.OPCITEMPROPERTIES input, bool deallocate) + { + TsCDaItemProperty[] output = null; + + if (input.dwNumProperties > 0) + { + output = new TsCDaItemProperty[input.dwNumProperties]; + + var pos = input.pItemProperties; + + for (var ii = 0; ii < output.Length; ii++) + { + try + { + output[ii] = GetItemProperty(pos, deallocate); + } + catch (Exception e) + { + output[ii] = new TsCDaItemProperty(); + output[ii].Description = e.Message; + output[ii].Result = OpcResult.E_FAIL; + } + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(input.pItemProperties); + input.pItemProperties = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Allocates and marshals an array of OPCITEMPROPERTIES structures. + /// + internal static OpcRcw.Da.OPCITEMPROPERTIES GetItemProperties(TsCDaItemProperty[] input) + { + var output = new OpcRcw.Da.OPCITEMPROPERTIES(); + + if (input != null && input.Length > 0) + { + output.hrErrorID = Result.S_OK; + output.dwReserved = 0; + output.dwNumProperties = input.Length; + output.pItemProperties = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY)) * input.Length); + + var error = false; + + var pos = output.pItemProperties; + + for (var ii = 0; ii < input.Length; ii++) + { + var property = GetItemProperty(input[ii]); + Marshal.StructureToPtr(property, pos, false); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMPROPERTY))); + + if (input[ii].Result.Failed()) + { + error = true; + } + } + + // set flag indicating one or more properties contained errors. + if (error) + { + output.hrErrorID = Result.S_FALSE; + } + } + + return output; + } + + /// + /// Unmarshals and deallocates a OPCITEMPROPERTY structures. + /// + internal static TsCDaItemProperty GetItemProperty(IntPtr pInput, bool deallocate) + { + TsCDaItemProperty output = null; + + if (pInput != IntPtr.Zero) + { + try + { + var property = (OpcRcw.Da.OPCITEMPROPERTY)Marshal.PtrToStructure(pInput, typeof(OpcRcw.Da.OPCITEMPROPERTY)); + + output = new TsCDaItemProperty(); + + output.ID = GetPropertyID(property.dwPropertyID); + output.Description = property.szDescription; + output.DataType = Technosoftware.DaAeHdaClient.Com.Interop.GetType((VarEnum)property.vtDataType); + output.ItemPath = null; + output.ItemName = property.szItemID; + output.Value = UnmarshalPropertyValue(output.ID, property.vValue); + output.Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(property.hrErrorID); + + // convert COM DA code to unified DA code. + if (property.hrErrorID == Result.E_BADRIGHTS) output.Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); + + } + catch (Exception) + { + } + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Da.OPCITEMPROPERTY)); + } + } + + return output; + } + + /// + /// Allocates and marshals an arary of OPCITEMPROPERTY structures. + /// + internal static OpcRcw.Da.OPCITEMPROPERTY GetItemProperty(TsCDaItemProperty input) + { + var output = new OpcRcw.Da.OPCITEMPROPERTY(); + + if (input != null) + { + output.dwPropertyID = input.ID.Code; + output.szDescription = input.Description; + output.vtDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(input.DataType); + output.vValue = MarshalPropertyValue(input.ID, input.Value); + output.wReserved = 0; + output.hrErrorID = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(input.Result); + + // set the property data type. + var description = TsDaPropertyDescription.Find(input.ID); + + if (description != null) + { + output.vtDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(description.Type); + } + + // convert unified DA code to COM DA code. + if (input.Result == OpcResult.Da.E_WRITEONLY) output.hrErrorID = Result.E_BADRIGHTS; + } + + return output; + } + + /// + public static TsDaPropertyID GetPropertyID(int input) + { + var fields = typeof(TsDaProperty).GetFields(BindingFlags.Static | BindingFlags.Public); + + foreach (var field in fields) + { + var property = (TsDaPropertyID)field.GetValue(typeof(TsDaPropertyID)); + + if (input == property.Code) + { + return property; + } + } + + return new TsDaPropertyID(input); + } + + /// + /// Converts the property value to a type supported by the unified interface. + /// + internal static object UnmarshalPropertyValue(TsDaPropertyID propertyID, object input) + { + if (input == null) return null; + + try + { + if (propertyID == TsDaProperty.DATATYPE) + { + return Technosoftware.DaAeHdaClient.Com.Interop.GetType((VarEnum)System.Convert.ToUInt16(input)); + } + + if (propertyID == TsDaProperty.ACCESSRIGHTS) + { + switch (System.Convert.ToInt32(input)) + { + case OpcRcw.Da.Constants.OPC_READABLE: return TsDaAccessRights.Readable; + case OpcRcw.Da.Constants.OPC_WRITEABLE: return TsDaAccessRights.Writable; + + case OpcRcw.Da.Constants.OPC_READABLE | OpcRcw.Da.Constants.OPC_WRITEABLE: + { + return TsDaAccessRights.ReadWritable; + } + } + + return null; + } + + if (propertyID == TsDaProperty.EUTYPE) + { + switch ((OpcRcw.Da.OPCEUTYPE)input) + { + case OpcRcw.Da.OPCEUTYPE.OPC_NOENUM: return TsDaEuType.NoEnum; + case OpcRcw.Da.OPCEUTYPE.OPC_ANALOG: return TsDaEuType.Analog; + case OpcRcw.Da.OPCEUTYPE.OPC_ENUMERATED: return TsDaEuType.Enumerated; + } + + return null; + } + + if (propertyID == TsDaProperty.QUALITY) + { + return new TsCDaQuality(System.Convert.ToInt16(input)); + } + + // convert UTC time in property to local time for the unified DA interface. + if (propertyID == TsDaProperty.TIMESTAMP) + { + if (input.GetType() == typeof(DateTime)) + { + var dateTime = (DateTime)input; + + if (dateTime != DateTime.MinValue) + { + return dateTime.ToLocalTime(); + } + + return dateTime; + } + } + } + catch { } + + return input; + } + + /// + /// Converts the property value to a type supported by COM-DA interface. + /// + internal static object MarshalPropertyValue(TsDaPropertyID propertyID, object input) + { + if (input == null) return null; + + try + { + if (propertyID == TsDaProperty.DATATYPE) + { + return (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType((Type)input); + } + + if (propertyID == TsDaProperty.ACCESSRIGHTS) + { + switch ((TsDaAccessRights)input) + { + case TsDaAccessRights.Readable: return OpcRcw.Da.Constants.OPC_READABLE; + case TsDaAccessRights.Writable: return OpcRcw.Da.Constants.OPC_WRITEABLE; + case TsDaAccessRights.ReadWritable: return OpcRcw.Da.Constants.OPC_READABLE | OpcRcw.Da.Constants.OPC_WRITEABLE; + } + + return null; + } + + if (propertyID == TsDaProperty.EUTYPE) + { + switch ((TsDaEuType)input) + { + case TsDaEuType.NoEnum: return OpcRcw.Da.OPCEUTYPE.OPC_NOENUM; + case TsDaEuType.Analog: return OpcRcw.Da.OPCEUTYPE.OPC_ANALOG; + case TsDaEuType.Enumerated: return OpcRcw.Da.OPCEUTYPE.OPC_ENUMERATED; + } + + return null; + } + + if (propertyID == TsDaProperty.QUALITY) + { + return ((TsCDaQuality)input).GetCode(); + } + + // convert local time in property to UTC time for the COM DA interface. + if (propertyID == TsDaProperty.TIMESTAMP) + { + if (input.GetType() == typeof(DateTime)) + { + var dateTime = (DateTime)input; + + if (dateTime != DateTime.MinValue) + { + return dateTime.ToUniversalTime(); + } + + return dateTime; + } + } + } + catch { } + + return input; + } + + /// + /// Converts an array of item values to an array of OPCITEMVQT objects. + /// + internal static OpcRcw.Da.OPCITEMVQT[] GetOPCITEMVQTs(TsCDaItemValue[] input) + { + OpcRcw.Da.OPCITEMVQT[] output = null; + + if (input != null) + { + output = new OpcRcw.Da.OPCITEMVQT[input.Length]; + + for (var ii = 0; ii < input.Length; ii++) + { + output[ii] = new OpcRcw.Da.OPCITEMVQT(); + + var timestamp = (input[ii].TimestampSpecified) ? input[ii].Timestamp : DateTime.MinValue; + + output[ii].vDataValue = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANT(input[ii].Value); + output[ii].bQualitySpecified = (input[ii].QualitySpecified) ? 1 : 0; + output[ii].wQuality = (input[ii].QualitySpecified) ? input[ii].Quality.GetCode() : (short)0; + output[ii].bTimeStampSpecified = (input[ii].TimestampSpecified) ? 1 : 0; + output[ii].ftTimeStamp = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(timestamp)); + } + + } + + return output; + } + + /// + /// Converts an array of item objects to an array of GetOPCITEMDEF objects. + /// + internal static OpcRcw.Da.OPCITEMDEF[] GetOPCITEMDEFs(TsCDaItem[] input) + { + OpcRcw.Da.OPCITEMDEF[] output = null; + + if (input != null) + { + output = new OpcRcw.Da.OPCITEMDEF[input.Length]; + + for (var ii = 0; ii < input.Length; ii++) + { + output[ii] = new OpcRcw.Da.OPCITEMDEF(); + + output[ii].szItemID = input[ii].ItemName; + output[ii].szAccessPath = (input[ii].ItemPath == null) ? string.Empty : input[ii].ItemPath; + output[ii].bActive = (input[ii].ActiveSpecified) ? ((input[ii].Active) ? 1 : 0) : 1; + output[ii].vtRequestedDataType = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(input[ii].ReqType); + output[ii].hClient = 0; + output[ii].dwBlobSize = 0; + output[ii].pBlob = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Unmarshals and deallocates a OPCITEMSTATE structures. + /// + internal static TsCDaItemValue[] GetItemValues(ref IntPtr pInput, int count, bool deallocate) + { + TsCDaItemValue[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCDaItemValue[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + var result = (OpcRcw.Da.OPCITEMSTATE)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMSTATE)); + + output[ii] = new TsCDaItemValue(); + output[ii].ClientHandle = result.hClient; + output[ii].Value = result.vDataValue; + output[ii].Quality = new TsCDaQuality(result.wQuality); + output[ii].QualitySpecified = true; + output[ii].Timestamp = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Convert(result.ftTimeStamp)); + output[ii].TimestampSpecified = output[ii].Timestamp != DateTime.MinValue; + + if (deallocate) + { + Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMSTATE)); + } + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Unmarshals and deallocates a OPCITEMRESULT structures. + /// + internal static int[] GetItemResults(ref IntPtr pInput, int count, bool deallocate) + { + int[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new int[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + var result = (OpcRcw.Da.OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OpcRcw.Da.OPCITEMRESULT)); + + output[ii] = result.hServer; + + if (deallocate) + { + Marshal.FreeCoTaskMem(result.pBlob); + result.pBlob = IntPtr.Zero; + + Marshal.DestroyStructure(pos, typeof(OpcRcw.Da.OPCITEMRESULT)); + } + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMRESULT))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Allocates and marshals an array of OPCBROWSEELEMENT structures. + /// + internal static IntPtr GetItemStates(TsCDaItemValueResult[] input) + { + var output = IntPtr.Zero; + + if (input != null && input.Length > 0) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE)) * input.Length); + + var pos = output; + + for (var ii = 0; ii < input.Length; ii++) + { + var item = new OpcRcw.Da.OPCITEMSTATE(); + + item.hClient = System.Convert.ToInt32(input[ii].ClientHandle); + item.vDataValue = input[ii].Value; + item.wQuality = (input[ii].QualitySpecified) ? input[ii].Quality.GetCode() : (short)0; + item.ftTimeStamp = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input[ii].Timestamp)); + item.wReserved = 0; + + Marshal.StructureToPtr(item, pos, false); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Da.OPCITEMSTATE))); + } + } + + return output; + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Da/Result.cs b/Technosoftware/DaAeHdaClient.Com/Da/Result.cs new file mode 100644 index 0000000..202ebae --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Da/Result.cs @@ -0,0 +1,128 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + namespace Da + { + /// + /// Defines all well known COM DA HRESULT codes. + /// + internal struct Result + { + /// + public const int S_OK = +0x00000000; // 0x00000000 + /// + public const int S_FALSE = +0x00000001; // 0x00000001 + /// + public const int E_NOTIMPL = -0x7FFFBFFF; // 0x80004001 + /// + public const int E_OUTOFMEMORY = -0x7FF8FFF2; // 0x8007000E + /// + public const int E_INVALIDARG = -0x7FF8FFA9; // 0x80070057 + /// + public const int E_NOINTERFACE = -0x7FFFBFFE; // 0x80004002 + /// + public const int E_POINTER = -0x7FFFBFFD; // 0x80004003 + /// + public const int E_FAIL = -0x7FFFBFFB; // 0x80004005 + /// + public const int CONNECT_E_NOCONNECTION = -0x7FFBFE00; // 0x80040200 + /// + public const int CONNECT_E_ADVISELIMIT = -0x7FFBFDFF; // 0x80040201 + /// + public const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005 + /// + public const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A + /// + public const int E_INVALIDHANDLE = -0x3FFBFFFF; // 0xC0040001 + /// + public const int E_BADTYPE = -0x3FFBFFFC; // 0xC0040004 + /// + public const int E_PUBLIC = -0x3FFBFFFB; // 0xC0040005 + /// + public const int E_BADRIGHTS = -0x3FFBFFFA; // 0xC0040006 + /// + public const int E_UNKNOWNITEMID = -0x3FFBFFF9; // 0xC0040007 + /// + public const int E_INVALIDITEMID = -0x3FFBFFF8; // 0xC0040008 + /// + public const int E_INVALIDFILTER = -0x3FFBFFF7; // 0xC0040009 + /// + public const int E_UNKNOWNPATH = -0x3FFBFFF6; // 0xC004000A + /// + public const int E_RANGE = -0x3FFBFFF5; // 0xC004000B + /// + public const int E_DUPLICATENAME = -0x3FFBFFF4; // 0xC004000C + /// + public const int S_UNSUPPORTEDRATE = +0x0004000D; // 0x0004000D + /// + public const int S_CLAMP = +0x0004000E; // 0x0004000E + /// + public const int S_INUSE = +0x0004000F; // 0x0004000F + /// + public const int E_INVALIDCONFIGFILE = -0x3FFBFFF0; // 0xC0040010 + /// + public const int E_NOTFOUND = -0x3FFBFFEF; // 0xC0040011 + /// + public const int E_INVALID_PID = -0x3FFBFDFD; // 0xC0040203 + /// + public const int E_DEADBANDNOTSET = -0x3FFBFC00; // 0xC0040400 + /// + public const int E_DEADBANDNOTSUPPORTED = -0x3FFBFBFF; // 0xC0040401 + /// + public const int E_NOBUFFERING = -0x3FFBFBFE; // 0xC0040402 + /// + public const int E_INVALIDCONTINUATIONPOINT = -0x3FFBFBFD; // 0xC0040403 + /// + public const int S_DATAQUEUEOVERFLOW = +0x00040404; // 0x00040404 + /// + public const int E_RATENOTSET = -0x3FFBFBFB; // 0xC0040405 + /// + public const int E_NOTSUPPORTED = -0x3FFBFBFA; // 0xC0040406 + } + } + + namespace Cpx + { + /// + /// Defines all well known Complex Data HRESULT codes. + /// + internal struct Result + { + /// + public const int E_TYPE_CHANGED = -0x3FFBFBF9; // 0xC0040407 + /// + public const int E_FILTER_DUPLICATE = -0x3FFBFBF8; // 0xC0040408 + /// + public const int E_FILTER_INVALID = -0x3FFBFBF7; // 0xC0040409 + /// + public const int E_FILTER_ERROR = -0x3FFBFBF6; // 0xC004040A + /// + public const int S_FILTER_NO_DATA = +0x0004040B; // 0xC004040B + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Da/Server.cs b/Technosoftware/DaAeHdaClient.Com/Da/Server.cs new file mode 100644 index 0000000..72019d2 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Da/Server.cs @@ -0,0 +1,984 @@ +#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.Threading; +using System.Collections; +using System.Globalization; +using System.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Da; +using Technosoftware.DaAeHdaClient.Utilities; + +using Technosoftware.OpcRcw.Da; +using Technosoftware.DaAeHdaClient.Com.Utilities; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Da +{ + /// + /// A .NET wrapper for a COM server that implements the DA server interfaces. + /// + internal class Server : Com.Server, ITsDaServer + { + #region Fields + + /// + /// The default result filters for the server. + /// + private int filters_ = (int)TsCDaResultFilter.All | (int)TsCDaResultFilter.ClientHandle; + + /// + /// A table of active subscriptions for the server. + /// + private readonly Hashtable subscriptions_ = new Hashtable(); + + #endregion + + #region Constructors + + /// + /// Initializes the object. + /// + internal Server() { } + + /// + /// Initializes the object with the specified COM server. + /// + internal Server(OpcUrl url, object server) + { + if (url == null) throw new ArgumentNullException(nameof(url)); + + url_ = (OpcUrl)url.Clone(); + server_ = server; + } + #endregion + + #region IDisposable Members + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected override void Dispose(bool disposing) + { + if (!disposed_) + { + lock (this) + { + if (disposing) + { + // Release managed resources. + if (server_ != null) + { + // release all groups. + foreach (Subscription subscription in subscriptions_.Values) + { + var methodName = "IOPCServer.RemoveGroup"; + + // remove subscription from server. + try + { + var state = subscription.GetState(); + if (state != null) + { + var server = BeginComCall(methodName, true); + server?.RemoveGroup((int)state.ServerHandle, 0); + } + } + catch + { + // Ignore error during Dispose + } + finally + { + EndComCall(methodName); + } + // dispose of the subscription object (disconnects all subscription connections). + subscription.Dispose(); + } + + // clear subscription table. + subscriptions_.Clear(); + } + } + + // Release unmanaged resources. + // Set large fields to null. + + if (server_ != null) + { + // release the COM server. + Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(server_); + server_ = null; + } + } + + // Call Dispose on your base class. + disposed_ = true; + } + + base.Dispose(disposing); + } + + private bool disposed_; + #endregion + + #region Technosoftware.DaAeHdaClient.Com.Server Overrides + /// + /// Returns the localized text for the specified result code. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + /// The result code identifier. + /// A message localized for the best match for the requested locale. + public override string GetErrorText(string locale, OpcResult resultId) + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCServer.GetErrorString"; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + + (server).GetErrorString( + resultId.Code, + Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(locale), + out var errorText); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + return errorText; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCServer.GetErrorString", e); + } + finally + { + EndComCall(methodName); + } + } + } + #endregion + + #region Technosoftware.DaAeHdaClient.IOpcServer Members + /// + /// 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() + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + 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) + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + filters_ = filters; + } + } + + /// + /// Returns the current server status. + /// + /// The current server status. + public OpcServerStatus GetServerStatus() + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCServer.GetStatus"; + + // initialize arguments. + IntPtr pStatus; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + (server).GetStatus(out pStatus); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // return status. + return Interop.GetServerStatus(ref pStatus, true); + } + } + + /// + /// Reads the current values for a set of items. + /// + /// The set of items to read. + /// The results of the read operation for each item. + public virtual TsCDaItemValueResult[] Read(TsCDaItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + var methodName = "IOPCItemIO.Read"; + if (server_ == null) throw new NotConnectedException(); + + var count = items.Length; + if (count == 0) throw new ArgumentOutOfRangeException(nameof(items.Length), @"0"); + + // initialize arguments. + var itemIDs = new string[count]; + var maxAges = new int[count]; + + for (var ii = 0; ii < count; ii++) + { + itemIDs[ii] = items[ii].ItemName; + maxAges[ii] = (items[ii].MaxAgeSpecified) ? items[ii].MaxAge : 0; + } + + var pValues = IntPtr.Zero; + var pQualities = IntPtr.Zero; + var pTimestamps = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + server.Read( + count, + itemIDs, + maxAges, + out pValues, + out pQualities, + out pTimestamps, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal results. + var values = Technosoftware.DaAeHdaClient.Com.Interop.GetVARIANTs(ref pValues, count, true); + var qualities = Technosoftware.DaAeHdaClient.Com.Interop.GetInt16s(ref pQualities, count, true); + var timestamps = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIMEs(ref pTimestamps, count, true); + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, count, true); + + // pre-fetch the current locale to use for data conversions. + var locale = GetLocale(); + + // construct result array. + var results = new TsCDaItemValueResult[count]; + + for (var ii = 0; ii < results.Length; ii++) + { + results[ii] = new TsCDaItemValueResult(items[ii]); + + results[ii].ServerHandle = null; + results[ii].Value = values[ii]; + results[ii].Quality = new TsCDaQuality(qualities[ii]); + results[ii].QualitySpecified = true; + results[ii].Timestamp = timestamps[ii]; + results[ii].TimestampSpecified = timestamps[ii] != DateTime.MinValue; + results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); } + + // convert the data type since the server does not support the feature. + if (results[ii].Value != null && items[ii].ReqType != null) + { + try + { + results[ii].Value = ChangeType(values[ii], items[ii].ReqType, locale); + } + catch (Exception e) + { + results[ii].Value = null; + results[ii].Quality = TsCDaQuality.Bad; + results[ii].QualitySpecified = true; + results[ii].Timestamp = DateTime.MinValue; + results[ii].TimestampSpecified = false; + + if (e.GetType() == typeof(OverflowException)) + { + results[ii].Result = Utilities.Interop.GetResultId(Result.E_RANGE); + } + else + { + results[ii].Result = Utilities.Interop.GetResultId(Result.E_BADTYPE); + } + } + } + + // apply request options. + if ((filters_ & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null; + if ((filters_ & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null; + if ((filters_ & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null; + + if ((filters_ & (int)TsCDaResultFilter.ItemTime) == 0) + { + results[ii].Timestamp = DateTime.MinValue; + results[ii].TimestampSpecified = false; + } + } + + // return results. + return results; + } + } + + /// + /// Writes the value, quality and timestamp for a set of items. + /// + /// The set of item values to write. + /// The results of the write operation for each item. + public virtual OpcItemResult[] Write(TsCDaItemValue[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCItemIO.WriteVQT"; + + var count = items.Length; + if (count == 0) throw new ArgumentOutOfRangeException("items.Length", "0"); + + // initialize arguments. + var itemIDs = new string[count]; + + for (var ii = 0; ii < count; ii++) + { + itemIDs[ii] = items[ii].ItemName; + } + + var values = Interop.GetOPCITEMVQTs(items); + + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + server.WriteVQT( + count, + itemIDs, + values, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal results. + var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true); + + // construct result array. + var results = new OpcItemResult[count]; + + for (var ii = 0; ii < count; ii++) + { + results[ii] = new OpcItemResult(items[ii]); + + results[ii].ServerHandle = null; + results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); } + + // apply request options. + if ((filters_ & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null; + if ((filters_ & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null; + if ((filters_ & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null; + } + + // return results. + return results; + } + } + + /// + /// Creates a new subscription. + /// + /// The initial state of the subscription. + /// The new subscription object. + public ITsCDaSubscription CreateSubscription(TsCDaSubscriptionState state) + { + if (state == null) throw new ArgumentNullException(nameof(state)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCServer.AddGroup"; + + // copy the subscription state. + var result = (TsCDaSubscriptionState)state.Clone(); + + // initialize arguments. + var iid = typeof(IOPCItemMgt).GUID; + object group = null; + + var serverHandle = 0; + var revisedUpdateRate = 0; + + var hDeadband = GCHandle.Alloc(result.Deadband, GCHandleType.Pinned); + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + server.AddGroup( + (result.Name != null) ? result.Name : "", + (result.Active) ? 1 : 0, + result.UpdateRate, + 0, + IntPtr.Zero, + hDeadband.AddrOfPinnedObject(), + Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(result.Locale), + out serverHandle, + out revisedUpdateRate, + ref iid, + out group); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + if (hDeadband.IsAllocated) + { + hDeadband.Free(); + } + EndComCall(methodName); + } + + if (group == null) throw new OpcResultException(OpcResult.E_FAIL, "The subscription was not created."); + + methodName = "IOPCGroupStateMgt2.SetKeepAlive"; + + // set the keep alive rate if requested. + try + { + var keepAlive = 0; + var comObject = BeginComCall(group, methodName, true); + comObject.SetKeepAlive(result.KeepAlive, out keepAlive); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + result.KeepAlive = keepAlive; + } + catch (Exception e1) + { + result.KeepAlive = 0; + ComCallError(methodName, e1); + } + finally + { + EndComCall(methodName); + } + + // save server handle. + result.ServerHandle = serverHandle; + + // set the revised update rate. + if (revisedUpdateRate > result.UpdateRate) + { + result.UpdateRate = revisedUpdateRate; + } + + // create the subscription object. + var subscription = CreateSubscription(group, result, filters_); + + // index by server handle. + subscriptions_[serverHandle] = subscription; + + // return subscription. + return subscription; + } + } + + /// + /// Cancels a subscription and releases all resources allocated for it. + /// + /// The subscription to cancel. + public void CancelSubscription(ITsCDaSubscription subscription) + { + if (subscription == null) throw new ArgumentNullException(nameof(subscription)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCServer.RemoveGroup"; + + // validate argument. + if (!typeof(Subscription).IsInstanceOfType(subscription)) + { + throw new ArgumentException("Incorrect object type.", nameof(subscription)); + } + + // get the subscription state. + var state = subscription.GetState(); + + if (!subscriptions_.ContainsKey(state.ServerHandle)) + { + throw new ArgumentException("Handle not found.", nameof(subscription)); + } + + subscriptions_.Remove(state.ServerHandle); + + // release all subscription resources. + subscription.Dispose(); + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + server.RemoveGroup((int)state.ServerHandle, 0); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + + /// + /// Fetches the children of a branch that meet the filter criteria. + /// + /// 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. + /// The set of elements found. + public virtual TsCDaBrowseElement[] Browse( + OpcItem itemId, + TsCDaBrowseFilters filters, + out TsCDaBrowsePosition position) + { + if (filters == null) throw new ArgumentNullException(nameof(filters)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCBrowse.Browse"; + + position = null; + + // initialize arguments. + var count = 0; + var moreElements = 0; + + var pContinuationPoint = IntPtr.Zero; + var pElements = IntPtr.Zero; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + server.Browse( + (itemId != null && itemId.ItemName != null) ? itemId.ItemName : "", + ref pContinuationPoint, + filters.MaxElementsReturned, + Interop.GetBrowseFilter(filters.BrowseFilter), + (filters.ElementNameFilter != null) ? filters.ElementNameFilter : "", + (filters.VendorFilter != null) ? filters.VendorFilter : "", + (filters.ReturnAllProperties) ? 1 : 0, + (filters.ReturnPropertyValues) ? 1 : 0, + (filters.PropertyIDs != null) ? filters.PropertyIDs.Length : 0, + Interop.GetPropertyIDs(filters.PropertyIDs), + out moreElements, + out count, + out pElements); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal results. + var elements = Interop.GetBrowseElements(ref pElements, count, true); + + var continuationPoint = Marshal.PtrToStringUni(pContinuationPoint); + Marshal.FreeCoTaskMem(pContinuationPoint); + + // check if more results exist. + if (moreElements != 0 || (continuationPoint != null && continuationPoint != "")) + { + // allocate new browse position object. + position = new BrowsePosition(itemId, filters, continuationPoint); + } + + // process results. + ProcessResults(elements, filters.PropertyIDs); + + return elements; + } + } + + /// + /// Continues a browse operation with previously specified search criteria. + /// + /// An object containing the browse operation state information. + /// The set of elements found. + public virtual TsCDaBrowseElement[] BrowseNext(ref TsCDaBrowsePosition position) + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCBrowse.Browse"; + + // check for valid position object. + if (position == null || position.GetType() != typeof(BrowsePosition)) + { + throw new BrowseCannotContinueException(); + } + + var pos = (BrowsePosition)position; + + // check for valid continuation point. + if (pos == null || pos.ContinuationPoint == null || pos.ContinuationPoint == "") + { + throw new BrowseCannotContinueException(); + } + + // initialize arguments. + var count = 0; + var moreElements = 0; + + var itemID = ((BrowsePosition)position).ItemID; + var filters = ((BrowsePosition)position).Filters; + + var pContinuationPoint = Marshal.StringToCoTaskMemUni(pos.ContinuationPoint); + var pElements = IntPtr.Zero; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + server.Browse( + (itemID != null && itemID.ItemName != null) ? itemID.ItemName : "", + ref pContinuationPoint, + filters.MaxElementsReturned, + Interop.GetBrowseFilter(filters.BrowseFilter), + (filters.ElementNameFilter != null) ? filters.ElementNameFilter : "", + (filters.VendorFilter != null) ? filters.VendorFilter : "", + (filters.ReturnAllProperties) ? 1 : 0, + (filters.ReturnPropertyValues) ? 1 : 0, + (filters.PropertyIDs != null) ? filters.PropertyIDs.Length : 0, + Interop.GetPropertyIDs(filters.PropertyIDs), + out moreElements, + out count, + out pElements); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal results. + var elements = Interop.GetBrowseElements(ref pElements, count, true); + + pos.ContinuationPoint = Marshal.PtrToStringUni(pContinuationPoint); + Marshal.FreeCoTaskMem(pContinuationPoint); + + // check if more no results exist. + if (moreElements == 0 && (pos.ContinuationPoint == null || pos.ContinuationPoint == "")) + { + position = null; + } + + // process results. + ProcessResults(elements, filters.PropertyIDs); + + return elements; + } + } + + /// + /// 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 virtual TsCDaItemPropertyCollection[] GetProperties( + OpcItem[] itemIds, + TsDaPropertyID[] propertyIDs, + bool returnValues) + { + if (itemIds == null) throw new ArgumentNullException(nameof(itemIds)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + var methodName = "IOPCBrowse.GetProperties"; + + // initialize arguments. + var pItemIDs = new string[itemIds.Length]; + + for (var ii = 0; ii < itemIds.Length; ii++) + { + pItemIDs[ii] = itemIds[ii].ItemName; + } + + var pPropertyLists = IntPtr.Zero; + + // invoke COM method. + try + { + var server = BeginComCall(methodName, true); + server.GetProperties( + itemIds.Length, + pItemIDs, + (returnValues) ? 1 : 0, + (propertyIDs != null) ? propertyIDs.Length : 0, + Interop.GetPropertyIDs(propertyIDs), + out pPropertyLists); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal results. + var resultLists = Interop.GetItemPropertyCollections(ref pPropertyLists, itemIds.Length, true); + + // replace integer codes with qnames passed in. + if (propertyIDs != null && propertyIDs.Length > 0) + { + foreach (var resultList in resultLists) + { + for (var ii = 0; ii < resultList.Count; ii++) + { + resultList[ii].ID = propertyIDs[ii]; + } + } + } + + // return the results. + return resultLists; + } + } + #endregion + + #region Private Methods + + /// + /// Converts a value to the specified type using the specified locale. + /// + protected object ChangeType(object source, Type type, string locale) + { + var culture = Thread.CurrentThread.CurrentCulture; + + // override the current thread culture to ensure conversions happen correctly. + try + { + Thread.CurrentThread.CurrentCulture = new CultureInfo(locale); + } + catch + { + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + } + + try + { + var result = OpcConvert.ChangeType(source, type); + + // check for overflow converting to float. + if (typeof(float) == type) + { + if (float.IsInfinity(Convert.ToSingle(result))) + { + throw new OverflowException(); + } + } + + return result; + } + + // restore the current thread culture after conversion. + finally + { + Thread.CurrentThread.CurrentCulture = culture; + } + } + + /// + /// Creates a new instance of a subscription. + /// + protected virtual Subscription CreateSubscription( + object group, + TsCDaSubscriptionState state, + int filters) + { + return new Subscription(group, state, filters); + } + + /// + /// Updates the properties to convert COM values to OPC .NET API results. + /// + private void ProcessResults(TsCDaBrowseElement[] elements, TsDaPropertyID[] propertyIds) + { + // check for null. + if (elements == null) + { + return; + } + + // process each element. + foreach (var element in elements) + { + // check if no properties. + if (element.Properties == null) + { + continue; + } + + // process each property. + foreach (var property in element.Properties) + { + // replace the property ids which on contain the codes with the proper qualified names passed in. + if (propertyIds != null) + { + foreach (var propertyId in propertyIds) + { + if (property.ID.Code == propertyId.Code) + { + property.ID = propertyId; + break; + } + } + } + } + } + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Da/Subscription.cs b/Technosoftware/DaAeHdaClient.Com/Da/Subscription.cs new file mode 100644 index 0000000..04a627e --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Da/Subscription.cs @@ -0,0 +1,2942 @@ +#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; +using System.Runtime.InteropServices; +using Technosoftware.DaAeHdaClient.Com.Utilities; +using Technosoftware.DaAeHdaClient.Da; +using Technosoftware.DaAeHdaClient.Utilities; +using Technosoftware.OpcRcw.Da; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Da +{ + /// + /// A .NET wrapper for a COM server that implements the DA subscription interfaces. + /// + internal class Subscription : ITsCDaSubscription + { + #region Constructors + /// + /// Initializes a new instance of a subscription. + /// + internal Subscription(object subscription, TsCDaSubscriptionState state, int filters) + { + if (subscription == null) throw new ArgumentNullException(nameof(subscription)); + if (state == null) throw new ArgumentNullException(nameof(state)); + + subscription_ = subscription; + name_ = state.Name; + _handle = state.ClientHandle; + _filters = filters; + callback_ = new Callback(state.ClientHandle, _filters, items_); + } + #endregion + + #region IDisposable Members + /// + /// The finalizer. + /// + ~Subscription() + { + Dispose(false); + } + + /// + /// Releases unmanaged resources held by the object. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!disposed_) + { + lock (lock_) + { + if (disposing) + { + // Free other state (managed objects). + + if (subscription_ != null) + { + // close all connections. + if (connection_ != null) + { + try + { + connection_.Dispose(); + } + catch + { + // Ignore. COM Server probably no longer connected + } + connection_ = null; + } + } + } + + // Free your own state (unmanaged objects). + // Set large fields to null. + + if (subscription_ != null) + { + // release subscription object. + try + { + Technosoftware.DaAeHdaClient.Com.Interop.ReleaseServer(subscription_); + } + catch + { + // Ignore. COM Server probably no longer connected + } + subscription_ = null; + } + } + + disposed_ = true; + } + } + #endregion + + #region Private Members + /// + /// The COM server for the subscription object. + /// + protected object subscription_; + + /// + /// A connect point with the COM server. + /// + protected ConnectionPoint connection_; + + /// + /// The internal object that implements the IOPCDataCallback interface. + /// + private Callback callback_; + + /// + /// The name of the subscription on the server. + /// + protected string name_; + + /// + /// A handle assigned by the client for the subscription. + /// + protected object _handle; + + /// + /// The default result filters for the subscription. + /// + protected int _filters = (int)TsCDaResultFilter.Minimal; + + /// + /// A table of all item identifers which are indexed by internal handle. + /// + private ItemTable items_ = new ItemTable(); + + /// + /// A counter used to assign unique internal client handles. + /// + protected int _counter; + + /// + /// The synchronization object for subscription access + /// + protected object lock_ = new object(); + + private int outstandingCalls_; + + private bool disposed_; + #endregion + + #region ISubscription Members + /// + /// An event to receive data change updates. + /// + public event TsCDaDataChangedEventHandler DataChangedEvent + { + add { lock (lock_) { callback_.DataChangedEvent += value; Advise(); } } + remove { lock (lock_) { callback_.DataChangedEvent -= value; Unadvise(); } } + } + + //====================================================================== + // Result Filters + + /// + /// 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() + { + lock (lock_) { 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) + { + lock (lock_) + { + _filters = filters; + + // update the callback object. + callback_.SetFilters(_handle, _filters); + } + } + + //====================================================================== + // State Management + + /// + /// Returns the current state of the subscription. + /// + /// The current state of the subscription. + public virtual TsCDaSubscriptionState GetState() + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCGroupStateMgt.GetState"; + var state = new TsCDaSubscriptionState { ClientHandle = _handle }; + + string name = null; + + try + { + var active = 0; + var updateRate = 0; + float deadband = 0; + var timebias = 0; + var localeID = 0; + var clientHandle = 0; + var serverHandle = 0; + + var subscription = BeginComCall(methodName, true); + subscription.GetState( + out updateRate, + out active, + out name, + out timebias, + out deadband, + out localeID, + out clientHandle, + out serverHandle); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + state.Name = name; + state.ServerHandle = serverHandle; + state.Active = active != 0; + state.UpdateRate = updateRate; + state.TimeBias = timebias; + state.Deadband = deadband; + state.Locale = Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(localeID); + + // cache the name separately. + name_ = state.Name; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + if (name != null) + { + methodName = "IOPCGroupStateMgt2.GetKeepAlive"; + try + { + var keepAlive = 0; + var subscription = BeginComCall(methodName, true); + subscription.GetKeepAlive(out keepAlive); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + state.KeepAlive = keepAlive; + } + catch (Exception e) + { + ComCallError(methodName, e); + state.KeepAlive = 0; + } + finally + { + EndComCall(methodName); + } + } + + return state; + } + } + + /// + /// Changes the state of a subscription. + /// + /// A bit mask that indicates which elements of the subscription state are changing. + /// The new subscription state. + /// The actual subscption state after applying the changes. + public TsCDaSubscriptionState ModifyState(int masks, TsCDaSubscriptionState state) + { + if (state == null) throw new ArgumentNullException(nameof(state)); + if (subscription_ == null) throw new NotConnectedException(); + + lock (lock_) + { + var methodName = "IOPCGroupStateMgt.SetName"; + // update the subscription name. + if ((masks & (int)TsCDaStateMask.Name) != 0 && state.Name != name_) + { + try + { + var subscription = BeginComCall(methodName, true); + subscription.SetName(state.Name); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + name_ = state.Name; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + // update the client handle. + if ((masks & (int)TsCDaStateMask.ClientHandle) != 0) + { + _handle = state.ClientHandle; + + // update the callback object. + callback_.SetFilters(_handle, _filters); + } + + // update the subscription state. + var active = (state.Active) ? 1 : 0; + var localeID = ((masks & (int)TsCDaStateMask.Locale) != 0) ? Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(state.Locale) : 0; + + var hActive = GCHandle.Alloc(active, GCHandleType.Pinned); + var hLocale = GCHandle.Alloc(localeID, GCHandleType.Pinned); + var hUpdateRate = GCHandle.Alloc(state.UpdateRate, GCHandleType.Pinned); + var hDeadband = GCHandle.Alloc(state.Deadband, GCHandleType.Pinned); + + var updateRate = 0; + + methodName = "IOPCGroupStateMgt.SetState"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.SetState( + ((masks & (int)TsCDaStateMask.UpdateRate) != 0) ? hUpdateRate.AddrOfPinnedObject() : IntPtr.Zero, + out updateRate, + ((masks & (int)TsCDaStateMask.Active) != 0) ? hActive.AddrOfPinnedObject() : IntPtr.Zero, + IntPtr.Zero, + ((masks & (int)TsCDaStateMask.Deadband) != 0) ? hDeadband.AddrOfPinnedObject() : IntPtr.Zero, + ((masks & (int)TsCDaStateMask.Locale) != 0) ? hLocale.AddrOfPinnedObject() : IntPtr.Zero, + IntPtr.Zero); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + if (hActive.IsAllocated) hActive.Free(); + if (hLocale.IsAllocated) hLocale.Free(); + if (hUpdateRate.IsAllocated) hUpdateRate.Free(); + if (hDeadband.IsAllocated) hDeadband.Free(); + EndComCall(methodName); + } + + // set keep alive, if supported. + if ((masks & (int)TsCDaStateMask.KeepAlive) != 0) + { + var keepAlive = 0; + + methodName = "IOPCGroupStateMgt2.SetKeepAlive"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.SetKeepAlive(state.KeepAlive, out keepAlive); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + state.KeepAlive = 0; + } + finally + { + EndComCall(methodName); + } + } + + // return the current state. + return GetState(); + } + } + + /// + /// Adds items to the subscription. + /// + /// The set of items to add to the subscription. + /// The results of the add item operation for each item. + public TsCDaItemResult[] AddItems(TsCDaItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (subscription_ == null) throw new NotConnectedException(); + + // check if nothing to do. + if (items.Length == 0) + { + return new TsCDaItemResult[0]; + } + + lock (lock_) + { + // marshal input parameters. + var count = items.Length; + + var definitions = Interop.GetOPCITEMDEFs(items); + TsCDaItemResult[] results = null; + + lock (items_) + { + for (var ii = 0; ii < count; ii++) + { + definitions[ii].hClient = ++_counter; + } + + // initialize output parameters. + var pResults = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + var methodName = "IOPCItemMgt.AddItems"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.AddItems( + count, + definitions, + out pResults, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal output parameters. + var serverHandles = Interop.GetItemResults(ref pResults, count, true); + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, count, true); + + // construct result list. + results = new TsCDaItemResult[count]; + + for (var ii = 0; ii < count; ii++) + { + // create a new Result. + results[ii] = new TsCDaItemResult(items[ii]); + + // save server handles. + results[ii].ServerHandle = serverHandles[ii]; + results[ii].ClientHandle = definitions[ii].hClient; + + // items created active by default. + if (!results[ii].ActiveSpecified) + { + results[ii].Active = true; + results[ii].ActiveSpecified = true; + } + + // update result id. + results[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]); + results[ii].DiagnosticInfo = null; + + // add new item table. + if (results[ii].Result.Succeeded()) + { + // save client handle. + results[ii].ClientHandle = items[ii].ClientHandle; + + items_[definitions[ii].hClient] = new OpcItem(results[ii]); + + // restore internal handle. + results[ii].ClientHandle = definitions[ii].hClient; + } + } + } + + // set non-critical item parameters - these methods all update the item result objects. + UpdateDeadbands(results); + UpdateSamplingRates(results); + SetEnableBuffering(results); + + lock (items_) + { + var filteredResults = (TsCDaItemResult[])items_.ApplyFilters(_filters, results); + + // need to return the client handle for failed items. + if ((_filters & (int)TsCDaResultFilter.ClientHandle) != 0) + { + for (var ii = 0; ii < count; ii++) + { + if (filteredResults[ii].Result.Failed()) + { + filteredResults[ii].ClientHandle = items[ii].ClientHandle; + } + } + } + + return filteredResults; + } + } + } + + /// + /// Modifies the state of items in the subscription + /// + /// Specifies which item state parameters are being modified. + /// The new state for each item. + /// The results of the modify item operation for each item. + public TsCDaItemResult[] ModifyItems(int masks, TsCDaItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (subscription_ == null) throw new NotConnectedException(); + + // check if nothing to do. + if (items.Length == 0) + { + return new TsCDaItemResult[0]; + } + + lock (lock_) + { + // initialize result list. + TsCDaItemResult[] results = null; + + lock (items_) + { + results = items_.CreateItems(items); + } + + if ((masks & (int)TsCDaStateMask.ReqType) != 0) SetReqTypes(results); + if ((masks & (int)TsCDaStateMask.Active) != 0) UpdateActive(results); + if ((masks & (int)TsCDaStateMask.Deadband) != 0) UpdateDeadbands(results); + if ((masks & (int)TsCDaStateMask.SamplingRate) != 0) UpdateSamplingRates(results); + if ((masks & (int)TsCDaStateMask.EnableBuffering) != 0) SetEnableBuffering(results); + + // return results. + lock (items_) + { + return (TsCDaItemResult[])items_.ApplyFilters(_filters, results); + } + } + } + + /// + /// Removes items from the subscription. + /// + /// The identifiers (i.e. server handles) for the items being removed. + /// The results of the remove item operation for each item. + public OpcItemResult[] RemoveItems(OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (subscription_ == null) throw new NotConnectedException(); + + // check if nothing to do. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + lock (lock_) + { + // get item ids. + OpcItem[] itemIDs = null; + + lock (items_) + { + itemIDs = items_.GetItemIDs(items); + } + + // fetch server handles. + var serverHandles = new int[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + serverHandles[ii] = (int)itemIDs[ii].ServerHandle; + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var methodName = "IOPCItemMgt.RemoveItems"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.RemoveItems(itemIDs.Length, serverHandles, out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal output parameters. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, itemIDs.Length, true); + + // process results. + var results = new OpcItemResult[itemIDs.Length]; + + var itemsToRemove = new ArrayList(itemIDs.Length); + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new OpcItemResult(itemIDs[ii]); + + results[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]); + results[ii].DiagnosticInfo = null; + + // flag item for removal from local list. + if (results[ii].Result.Succeeded()) + { + itemsToRemove.Add(results[ii].ClientHandle); + } + } + + // apply filter to results. + lock (items_) + { + results = (OpcItemResult[])items_.ApplyFilters(_filters, results); + + // remove item from local list. + foreach (int clientHandle in itemsToRemove) + { + items_[clientHandle] = null; + } + + return results; + } + } + } + + /// + /// Reads the values for a set of items in the subscription. + /// + /// The identifiers (i.e. server handles) for the items being read. + /// The value for each of items. + public TsCDaItemValueResult[] Read(TsCDaItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (subscription_ == null) throw new NotConnectedException(); + + // check if nothing to do. + if (items.Length == 0) + { + return new TsCDaItemValueResult[0]; + } + + lock (lock_) + { + // get item ids. + OpcItem[] itemIDs = null; + + lock (items_) + { + itemIDs = items_.GetItemIDs(items); + } + + // read from the server. + var results = Read(itemIDs, items); + + // return results. + lock (items_) + { + return (TsCDaItemValueResult[])items_.ApplyFilters(_filters, results); + } + } + } + + /// + /// Writes the value, quality and timestamp for a set of items in the subscription. + /// + /// The item values to write. + /// The results of the write operation for each item. + public OpcItemResult[] Write(TsCDaItemValue[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (subscription_ == null) throw new NotConnectedException(); + + // check if nothing to do. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + lock (lock_) + { + // get item ids. + OpcItem[] itemIDs = null; + + lock (items_) + { + itemIDs = items_.GetItemIDs(items); + } + + // write to the server. + var results = Write(itemIDs, items); + + // return results. + lock (items_) + { + return (OpcItemResult[])items_.ApplyFilters(_filters, results); + } + } + } + + //====================================================================== + // Asynchronous I/O + + /// + /// Begins an asynchronous read operation for a set of items. + /// + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Read( + TsCDaItem[] items, + object requestHandle, + TsCDaReadCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + if (subscription_ == null) throw new NotConnectedException(); + + request = null; + + // check if nothing to do. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + lock (lock_) + { + // ensure a callback connection is established with the server. + if (connection_ == null) + { + Advise(); + } + + // get item ids. + OpcItem[] itemIDs = null; + + lock (items_) + { + itemIDs = items_.GetItemIDs(items); + } + + // create request object. + var internalRequest = new Request( + this, + requestHandle, + _filters, + _counter++, + callback); + + // register request with callback object. + callback_.BeginRequest(internalRequest); + request = internalRequest; + + // begin read request. + OpcItemResult[] results = null; + var cancelID = 0; + + try + { + results = BeginRead(itemIDs, items, internalRequest.RequestID, out cancelID); + } + catch (Exception) + { + callback_.EndRequest(internalRequest); + throw; + } + + // apply request options. + lock (items_) + { + items_.ApplyFilters(_filters | (int)TsCDaResultFilter.ClientHandle, results); + } + + lock (internalRequest) + { + // check if all results have already arrived - this invokes the callback if this is the case. + if (internalRequest.BeginRead(cancelID, results)) + { + callback_.EndRequest(internalRequest); + request = null; + } + } + + // return initial results. + return results; + } + } + + /// + /// Begins an asynchronous write operation for a set of items. + /// + /// The set of item values to write (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Write( + TsCDaItemValue[] items, + object requestHandle, + TsCDaWriteCompleteEventHandler callback, + out IOpcRequest request) + { + + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + if (subscription_ == null) throw new NotConnectedException(); + + request = null; + + // check if nothing to do. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + lock (lock_) + { + // ensure a callback connection is established with the server. + if (connection_ == null) + { + Advise(); + } + + // get item ids. + OpcItem[] itemIDs = null; + + lock (items_) + { + itemIDs = items_.GetItemIDs(items); + } + + // create request object. + var internalRequest = new Request( + this, + requestHandle, + _filters, + _counter++, + callback); + + // register request with callback object. + callback_.BeginRequest(internalRequest); + request = internalRequest; + + // begin write request. + OpcItemResult[] results = null; + var cancelID = 0; + + try + { + results = BeginWrite(itemIDs, items, internalRequest.RequestID, out cancelID); + } + catch (Exception) + { + callback_.EndRequest(internalRequest); + throw; + } + + // apply request options. + lock (items_) + { + items_.ApplyFilters(_filters | (int)TsCDaResultFilter.ClientHandle, results); + } + + lock (internalRequest) + { + // check if all results have already arrived - this invokes the callback if this is the case. + if (internalRequest.BeginWrite(cancelID, results)) + { + callback_.EndRequest(internalRequest); + request = null; + } + } + + // return initial results. + return results; + } + } + + /// + /// Cancels an asynchronous read or write operation. + /// + /// The object returned from the BeginRead or BeginWrite request. + /// The function to invoke when the cancel completes. + public void Cancel(IOpcRequest request, TsCDaCancelCompleteEventHandler callback) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + lock (lock_) + { + lock (request) + { + // check if request can still be cancelled. + if (!callback_.CancelRequest((Request)request)) + { + return; + } + + // update the callback. + ((Request)request).Callback = callback; + + // send a cancel request to the server. + var methodName = "IOPCAsyncIO2.Cancel2"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.Cancel2(((Request)request).CancelID); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + } + + /// + /// Causes the server to send a data changed notification for all active items. + /// + public virtual void Refresh() + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCAsyncIO3.RefreshMaxAge"; + try + { + var cancelID = 0; + var subscription = BeginComCall(methodName, true); + subscription.RefreshMaxAge(int.MaxValue, ++_counter, out cancelID); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + + /// + /// Causes the server to send a data changed notification for all active items. + /// + /// An identifier for the request assigned by the caller. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public virtual void Refresh( + object requestHandle, + out IOpcRequest request) + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + // ensure a callback connection is established with the server. + if (connection_ == null) + { + Advise(); + } + + // create request object. + var internalRequest = new Request( + this, + requestHandle, + _filters, + _counter++, + null); + + var cancelID = 0; + + var methodName = "IOPCAsyncIO3.RefreshMaxAge"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.RefreshMaxAge(0, (int)internalRequest.RequestID, out cancelID); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + request = internalRequest; + + // save the cancel id. + lock (request) + { + internalRequest.BeginRefresh(cancelID); + } + } + } + + /// + /// Enables or disables data change notifications from the server. + /// + /// Whether data change notifications are enabled. + public virtual void SetEnabled(bool enabled) + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCAsyncIO3.SetEnable"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.SetEnable((enabled) ? 1 : 0); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + + /// + /// Checks whether data change notifications from the server are enabled. + /// + /// Whether data change notifications are enabled. + public virtual bool GetEnabled() + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCAsyncIO3.GetEnable"; + try + { + var enabled = 0; + var subscription = BeginComCall(methodName, true); + subscription.GetEnable(out enabled); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + return enabled != 0; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + #endregion + + #region COM Call Tracing + /// + /// Must be called before any COM call. + /// + /// The interface to used when making the call. + /// Name of the method. + /// if set to true interface is an required interface and and exception is thrown on error. + /// + protected T BeginComCall(string methodName, bool isRequiredInterface) where T : class + { + Utils.Trace(Utils.TraceMasks.ExternalSystem, "{0} called.", methodName); + + lock (lock_) + { + outstandingCalls_++; + + if (subscription_ == null) + { + if (isRequiredInterface) + { + throw new NotConnectedException(); + } + } + + var comObject = subscription_ as T; + + if (comObject == null) + { + if (isRequiredInterface) + { + throw new NotSupportedException(Utils.Format("OPC Interface '{0}' is a required interface but not supported by the server.", typeof(T).Name)); + } + else + { + Utils.Trace(Utils.TraceMasks.ExternalSystem, "OPC Interface '{0}' is not supported by server but it is an optional one.", typeof(T).Name); + } + } + + DCOMCallWatchdog.Set(); + + return comObject; + } + } + + /// + /// Must called if a COM call returns an unexpected exception. + /// + /// Name of the method. + /// The exception. + /// Note that some COM calls are expected to return errors. + protected void ComCallError(string methodName, Exception e) + { + SafeNativeMethods.TraceComError(e, methodName); + } + + /// + /// Must be called in the finally block after making a COM call. + /// + /// Name of the method. + protected void EndComCall(string methodName) + { + Utils.Trace(Utils.TraceMasks.ExternalSystem, "{0} completed.", methodName); + + lock (lock_) + { + outstandingCalls_--; + + DCOMCallWatchdog.Reset(); + } + } + #endregion + + #region Private Methods + /// + /// Reads a set of items using DA3.0 interfaces. + /// + protected virtual TsCDaItemValueResult[] Read(OpcItem[] itemIDs, TsCDaItem[] items) + { + var methodName = "IOPCSyncIO2.ReadMaxAge"; + try + { + // marshal input parameters. + var serverHandles = new int[itemIDs.Length]; + var maxAges = new int[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + serverHandles[ii] = (int)itemIDs[ii].ServerHandle; + maxAges[ii] = (items[ii].MaxAgeSpecified) ? items[ii].MaxAge : 0; + } + + // initialize output parameters. + var pValues = IntPtr.Zero; + var pQualities = IntPtr.Zero; + var pTimestamps = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.ReadMaxAge( + itemIDs.Length, + serverHandles, + maxAges, + out pValues, + out pQualities, + out pTimestamps, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // unmarshal output parameters. + var values = Com.Interop.GetVARIANTs(ref pValues, itemIDs.Length, true); + var qualities = Technosoftware.DaAeHdaClient.Com.Interop.GetInt16s(ref pQualities, itemIDs.Length, true); + var timestamps = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIMEs(ref pTimestamps, itemIDs.Length, true); + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, itemIDs.Length, true); + + // create item results. + var results = new TsCDaItemValueResult[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new TsCDaItemValueResult(itemIDs[ii]); + + results[ii].Value = values[ii]; + results[ii].Quality = new TsCDaQuality(qualities[ii]); + results[ii].QualitySpecified = values[ii] != null; + results[ii].Timestamp = timestamps[ii]; + results[ii].TimestampSpecified = values[ii] != null; + results[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); } + } + + // return results. + return results; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Writes a set of items using DA3.0 interfaces. + /// + protected virtual OpcItemResult[] Write(OpcItem[] itemIDs, TsCDaItemValue[] items) + { + var methodName = "IOPCSyncIO2.WriteVQT"; + try + { + // initialize input parameters. + var serverHandles = new int[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + serverHandles[ii] = (int)itemIDs[ii].ServerHandle; + } + + var values = Interop.GetOPCITEMVQTs(items); + + // write to sever. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.WriteVQT( + itemIDs.Length, + serverHandles, + values, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // unmarshal results. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, itemIDs.Length, true); + + // create result list. + var results = new OpcItemResult[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new OpcItemResult(itemIDs[ii]); + + results[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); } + } + + // return results. + return results; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Begins an asynchronous read of a set of items using DA3.0 interfaces. + /// + protected virtual OpcItemResult[] BeginRead( + OpcItem[] itemIDs, + TsCDaItem[] items, + int requestID, + out int cancelID) + { + var methodName = "IOPCAsyncIO3.ReadMaxAge"; + try + { + // marshal input parameters. + var serverHandles = new int[itemIDs.Length]; + var maxAges = new int[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + serverHandles[ii] = (int)itemIDs[ii].ServerHandle; + maxAges[ii] = (items[ii].MaxAgeSpecified) ? items[ii].MaxAge : 0; + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.ReadMaxAge( + itemIDs.Length, + serverHandles, + maxAges, + requestID, + out cancelID, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // unmarshal output parameters. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, itemIDs.Length, true); + + // create item results. + var results = new OpcItemResult[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new OpcItemResult(itemIDs[ii]); + + results[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); } + } + + // return results. + return results; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Begins an asynchronous write for a set of items using DA3.0 interfaces. + /// + protected virtual OpcItemResult[] BeginWrite( + OpcItem[] itemIDs, + TsCDaItemValue[] items, + int requestID, + out int cancelID) + { + var methodName = "IOPCAsyncIO3.WriteVQT"; + try + { + // initialize input parameters. + var serverHandles = new int[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + serverHandles[ii] = (int)itemIDs[ii].ServerHandle; + } + + var values = Interop.GetOPCITEMVQTs(items); + + // write to sever. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.WriteVQT( + itemIDs.Length, + serverHandles, + values, + requestID, + out cancelID, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // unmarshal results. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, itemIDs.Length, true); + + // create result list. + var results = new OpcItemResult[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new OpcItemResult(itemIDs[ii]); + + results[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); } + } + + // return results. + return results; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException("IOPCAsyncIO3.WriteVQT()", e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Sets the requested data type for the specified items. + /// + private void SetReqTypes(TsCDaItemResult[] items) + { + // check if there is nothing to do. + if (items == null || items.Length == 0) return; + + // clients must explicitly set the ReqType to typeof(object) in order to set it to VT_EMPTY. + var changedItems = new ArrayList(); + + foreach (var item in items) + { + if (item.Result.Succeeded()) + { + if (item.ReqType != null) changedItems.Add(item); + } + } + + // check if there is nothing to do. + if (changedItems.Count == 0) return; + + // invoke method. + var methodName = "IOPCItemMgt.SetDatatypes"; + try + { + // initialize input parameters. + var handles = new int[changedItems.Count]; + var datatypes = new short[changedItems.Count]; + + for (var ii = 0; ii < changedItems.Count; ii++) + { + var item = (TsCDaItemResult)changedItems[ii]; + handles[ii] = Convert.ToInt32(item.ServerHandle); + datatypes[ii] = (short)Technosoftware.DaAeHdaClient.Com.Interop.GetType(item.ReqType); + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.SetDatatypes( + changedItems.Count, + handles, + datatypes, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // check for individual item errors. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, handles.Length, true); + + for (var ii = 0; ii < errors.Length; ii++) + { + if (Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]).Failed()) + { + var item = (TsCDaItemResult)changedItems[ii]; + item.Result = OpcResult.Da.E_BADTYPE; + item.DiagnosticInfo = null; + } + } + } + + // treat any general failure to mean the item is deactivated. + catch (Exception e) + { + for (var ii = 0; ii < changedItems.Count; ii++) + { + var item = (TsCDaItemResult)changedItems[ii]; + item.Result = OpcResult.Da.E_BADTYPE; + item.DiagnosticInfo = null; + } + ComCallError(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Sets the active state for the specified items. + /// + private void SetActive(TsCDaItemResult[] items, bool active) + { + // check if there is nothing to do. + if (items == null || items.Length == 0) return; + + // invoke method. + var methodName = "IOPCItemMgt.SetActiveState"; + try + { + // initialize input parameters. + var handles = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + handles[ii] = Convert.ToInt32(items[ii].ServerHandle); + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.SetActiveState( + items.Length, + handles, + (active) ? 1 : 0, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // check for individual item errors. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, handles.Length, true); + + for (var ii = 0; ii < errors.Length; ii++) + { + if (Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]).Failed()) + { + items[ii].Active = false; + items[ii].ActiveSpecified = true; + } + } + } + + // treat any general failure to mean the item is deactivated. + catch (Exception e) + { + for (var ii = 0; ii < items.Length; ii++) + { + items[ii].Active = false; + items[ii].ActiveSpecified = true; + ComCallError(methodName, e); + } + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Update the active state for the specified items. + /// + private void UpdateActive(TsCDaItemResult[] items) + { + if (items == null || items.Length == 0) return; + + // seperate items in two groups depending on whether the deadband is being set or cleared. + var activatedItems = new ArrayList(); + var deactivatedItems = new ArrayList(); + + foreach (var item in items) + { + if (item.Result.Succeeded() && item.ActiveSpecified) + { + if (item.Active) + { + activatedItems.Add(item); + } + else + { + deactivatedItems.Add(item); + } + } + } + + // activate items. + SetActive((TsCDaItemResult[])activatedItems.ToArray(typeof(TsCDaItemResult)), true); + + // de-activate items. + SetActive((TsCDaItemResult[])deactivatedItems.ToArray(typeof(TsCDaItemResult)), false); + } + + /// + /// Sets the deadbands for the specified items. + /// + private void SetDeadbands(TsCDaItemResult[] items) + { + // check if there is nothing to do. + if (items == null || items.Length == 0) return; + + // invoke method. + var methodName = "IOPCItemDeadbandMgt.SetItemDeadband"; + try + { + // initialize input parameters. + var handles = new int[items.Length]; + var deadbands = new float[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + handles[ii] = Convert.ToInt32(items[ii].ServerHandle); + deadbands[ii] = items[ii].Deadband; + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.SetItemDeadband( + handles.Length, + handles, + deadbands, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // check for individual item errors. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, handles.Length, true); + + for (var ii = 0; ii < errors.Length; ii++) + { + if (Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]).Failed()) + { + items[ii].Deadband = 0; + items[ii].DeadbandSpecified = false; + } + } + } + + // treat any general failure as an indication that deadband is not supported. + catch (Exception e) + { + for (var ii = 0; ii < items.Length; ii++) + { + items[ii].Deadband = 0; + items[ii].DeadbandSpecified = false; + } + ComCallError(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Clears the deadbands for the specified items. + /// + private void ClearDeadbands(TsCDaItemResult[] items) + { + // check if there is nothing to do. + if (items == null || items.Length == 0) return; + + // invoke method. + var methodName = "IOPCItemDeadbandMgt.ClearItemDeadband"; + try + { + // initialize input parameters. + var handles = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + handles[ii] = Convert.ToInt32(items[ii].ServerHandle); + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.ClearItemDeadband( + handles.Length, + handles, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // check for individual item errors. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, handles.Length, true); + + for (var ii = 0; ii < errors.Length; ii++) + { + if (Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]).Failed()) + { + items[ii].Deadband = 0; + items[ii].DeadbandSpecified = false; + } + } + } + + // treat any general failure as an indication that deadband is not supported. + catch (Exception e) + { + for (var ii = 0; ii < items.Length; ii++) + { + items[ii].Deadband = 0; + items[ii].DeadbandSpecified = false; + } + ComCallError(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Update the deadbands for the specified items. + /// + private void UpdateDeadbands(TsCDaItemResult[] items) + { + if (items == null || items.Length == 0) return; + + // seperate items in two groups depending on whether the deadband is being set or cleared. + var changedItems = new ArrayList(); + var clearedItems = new ArrayList(); + + foreach (var item in items) + { + if (item.Result.Succeeded()) + { + if (item.DeadbandSpecified) + { + changedItems.Add(item); + } + else + { + clearedItems.Add(item); + } + } + } + + // set deadbands. + SetDeadbands((TsCDaItemResult[])changedItems.ToArray(typeof(TsCDaItemResult))); + + // clear deadbands. + ClearDeadbands((TsCDaItemResult[])clearedItems.ToArray(typeof(TsCDaItemResult))); + } + + /// + /// Sets the sampling rates for the specified items. + /// + private void SetSamplingRates(TsCDaItemResult[] items) + { + // check if there is nothing to do. + if (items == null || items.Length == 0) return; + + // invoke method. + var methodName = "IOPCItemSamplingMgt.SetItemSamplingRate"; + try + { + // initialize input parameters. + var handles = new int[items.Length]; + var samplingRate = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + handles[ii] = Convert.ToInt32(items[ii].ServerHandle); + samplingRate[ii] = items[ii].SamplingRate; + } + + // initialize output parameters. + var pResults = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, false); + if (subscription != null) + { + subscription.SetItemSamplingRate( + handles.Length, + handles, + samplingRate, + out pResults, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // check for individual item errors. + var results = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pResults, handles.Length, true); + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, handles.Length, true); + + for (var ii = 0; ii < errors.Length; ii++) + { + if (items[ii].SamplingRate != results[ii]) + { + items[ii].SamplingRate = results[ii]; + items[ii].SamplingRateSpecified = true; + continue; + } + + if (Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]).Failed()) + { + items[ii].SamplingRate = 0; + items[ii].SamplingRateSpecified = false; + continue; + } + } + } + } + + // treat any general failure as an indication that sampling rate is not supported. + catch (Exception e) + { + for (var ii = 0; ii < items.Length; ii++) + { + items[ii].SamplingRate = 0; + items[ii].SamplingRateSpecified = false; + } + ComCallError(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Clears the sampling rates for the specified items. + /// + private void ClearSamplingRates(TsCDaItemResult[] items) + { + // check if there is nothing to do. + if (items == null || items.Length == 0) return; + + // invoke method. + var methodName = "IOPCItemSamplingMgt.ClearItemSamplingRate"; + try + { + // initialize input parameters. + var handles = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + handles[ii] = Convert.ToInt32(items[ii].ServerHandle); + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, false); + if (subscription != null) + { + subscription.ClearItemSamplingRate( + handles.Length, + handles, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // check for individual item errors. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, handles.Length, true); + + for (var ii = 0; ii < errors.Length; ii++) + { + if (Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]).Failed()) + { + items[ii].SamplingRate = 0; + items[ii].SamplingRateSpecified = false; + } + } + } + } + + // treat any general failure as an indication that sampling rate is not supported. + catch (Exception e) + { + for (var ii = 0; ii < items.Length; ii++) + { + items[ii].SamplingRate = 0; + items[ii].SamplingRateSpecified = false; + } + ComCallError(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Update the sampling rates for the specified items. + /// + private void UpdateSamplingRates(TsCDaItemResult[] items) + { + if (items == null || items.Length == 0) return; + + // seperate items in two groups depending on whether the sampling rate is being set or cleared. + var changedItems = new ArrayList(); + var clearedItems = new ArrayList(); + + foreach (var item in items) + { + if (item.Result.Succeeded()) + { + if (item.SamplingRateSpecified) + { + changedItems.Add(item); + } + else + { + clearedItems.Add(item); + } + } + } + + // set sampling rates. + SetSamplingRates((TsCDaItemResult[])changedItems.ToArray(typeof(TsCDaItemResult))); + + // clear sampling rates. + ClearSamplingRates((TsCDaItemResult[])clearedItems.ToArray(typeof(TsCDaItemResult))); + } + + /// + /// Sets the enable buffering flags. + /// + private void SetEnableBuffering(TsCDaItemResult[] items) + { + // check if there is nothing to do. + if (items == null || items.Length == 0) return; + + var changedItems = new ArrayList(); + + foreach (var item in items) + { + if (item.Result.Succeeded()) + { + changedItems.Add(item); + } + } + + // check if there is nothing to do. + if (changedItems.Count == 0) return; + + // invoke method. + var methodName = "IOPCItemSamplingMgt.SetItemBufferEnable"; + try + { + // initialize input parameters. + var handles = new int[changedItems.Count]; + var enabled = new int[changedItems.Count]; + + for (var ii = 0; ii < changedItems.Count; ii++) + { + var item = (TsCDaItemResult)changedItems[ii]; + handles[ii] = Convert.ToInt32(item.ServerHandle); + enabled[ii] = (item.EnableBufferingSpecified && item.EnableBuffering) ? 1 : 0; + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, false); + if (subscription != null) + { + subscription.SetItemBufferEnable( + handles.Length, + handles, + enabled, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // check for individual item errors. + var errors = Technosoftware.DaAeHdaClient.Com.Interop.GetInt32s(ref pErrors, handles.Length, true); + + for (var ii = 0; ii < errors.Length; ii++) + { + var item = (TsCDaItemResult)changedItems[ii]; + + if (Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(errors[ii]).Failed()) + { + item.EnableBuffering = false; + item.EnableBufferingSpecified = true; + } + } + } + } + + // treat any general failure as an indication that enable buffering is not supported. + catch (Exception e) + { + foreach (TsCDaItemResult item in changedItems) + { + item.EnableBuffering = false; + item.EnableBufferingSpecified = true; + } + ComCallError(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Establishes a connection point callback with the COM server. + /// + private void Advise() + { + if (connection_ == null) + { + connection_ = new ConnectionPoint(subscription_, typeof(IOPCDataCallback).GUID); + connection_.Advise(callback_); + } + } + + /// + /// Closes a connection point callback with the COM server. + /// + private void Unadvise() + { + if (connection_ != null) + { + if (connection_.Unadvise() == 0) + { + connection_.Dispose(); + connection_ = null; + } + } + } + #endregion + + #region ItemTable Class + /// + /// A table of item identifiers indexed by internal handle. + /// + private class ItemTable + { + /// + /// Looks up an item identifier for the specified internal handle. + /// + public OpcItem this[object handle] + { + get + { + if (handle != null) + { + return (OpcItem)items_[handle]; + } + + return null; + } + + set + { + if (handle != null) + { + if (value == null) + { + items_.Remove(handle); + return; + } + + items_[handle] = value; + } + } + } + + /// + /// Returns a server handle that must be treated as invalid by the server, + /// + /// + private int GetInvalidHandle() + { + var invalidHandle = 0; + + foreach (OpcItem item in items_.Values) + { + if (item.ServerHandle != null && item.ServerHandle.GetType() == typeof(int)) + { + if (invalidHandle < (int)item.ServerHandle) + { + invalidHandle = (int)item.ServerHandle + 1; + } + } + } + + return invalidHandle; + } + + /// + /// Copies a set of items an substitutes the client and server handles for use by the server. + /// + public OpcItem[] GetItemIDs(OpcItem[] items) + { + // create an invalid server handle. + var invalidHandle = GetInvalidHandle(); + + // copy the items. + var itemIDs = new OpcItem[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + // lookup server handle. + var itemID = this[items[ii].ServerHandle]; + + // copy the item id. + if (itemID != null) + { + itemIDs[ii] = (OpcItem)itemID.Clone(); + } + + else + { + itemIDs[ii] = new OpcItem(); + itemIDs[ii].ServerHandle = invalidHandle; + } + + // store the internal handle as the client handle. + itemIDs[ii].ClientHandle = items[ii].ServerHandle; + } + + // return the item copies. + return itemIDs; + } + + /// + /// Creates a item result list from a set of items and sets the handles for use by the server. + /// + public TsCDaItemResult[] CreateItems(TsCDaItem[] items) + { + if (items == null) { return null; } + + var results = new TsCDaItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + // initialize result with the item + results[ii] = new TsCDaItemResult((TsCDaItem)items[ii]); + + // lookup the cached identifier. + var itemID = this[items[ii].ServerHandle]; + + if (itemID != null) + { + results[ii].ItemName = itemID.ItemName; + results[ii].ItemPath = itemID.ItemName; + results[ii].ServerHandle = itemID.ServerHandle; + + // update the client handle. + itemID.ClientHandle = items[ii].ClientHandle; + } + + // check if handle not found. + if (results[ii].ServerHandle == null) + { + results[ii].Result = OpcResult.Da.E_INVALIDHANDLE; + results[ii].DiagnosticInfo = null; + continue; + } + + // replace client handle with internal handle. + results[ii].ClientHandle = items[ii].ServerHandle; + } + + return results; + } + + /// + /// Updates a result list based on the request options and sets the handles for use by the client. + /// + public OpcItem[] ApplyFilters(int filters, OpcItem[] results) + { + if (results == null) { return null; } + + foreach (var result in results) + { + var itemID = this[result.ClientHandle]; + + if (itemID != null) + { + result.ItemName = ((filters & (int)TsCDaResultFilter.ItemName) != 0) ? itemID.ItemName : null; + result.ItemPath = ((filters & (int)TsCDaResultFilter.ItemPath) != 0) ? itemID.ItemPath : null; + result.ServerHandle = result.ClientHandle; + result.ClientHandle = ((filters & (int)TsCDaResultFilter.ClientHandle) != 0) ? itemID.ClientHandle : null; + } + + if ((filters & (int)TsCDaResultFilter.ItemTime) == 0) + { + if (result.GetType() == typeof(TsCDaItemValueResult)) + { + ((TsCDaItemValueResult)result).Timestamp = DateTime.MinValue; + ((TsCDaItemValueResult)result).TimestampSpecified = false; + } + } + } + + return results; + } + + /// + /// The table of known item identifiers. + /// + private Hashtable items_ = new Hashtable(); + } + #endregion + + #region IOPCDataCallback Members + /// + /// A class that implements the IOPCDataCallback interface. + /// + private class Callback : IOPCDataCallback + { + /// + /// Initializes the object with the containing subscription object. + /// + public Callback(object handle, int filters, ItemTable items) + { + handle_ = handle; + filters_ = filters; + items_ = items; + } + + /// + /// Updates the result filters and subscription handle. + /// + public void SetFilters(object handle, int filters) + { + lock (lock_) + { + handle_ = handle; + filters_ = filters; + } + } + + /// + /// Adds an asynchrounous request. + /// + public void BeginRequest(Request request) + { + lock (lock_) + { + requests_[request.RequestID] = request; + } + } + + /// + /// Returns true is an asynchrounous request can be cancelled. + /// + public bool CancelRequest(Request request) + { + lock (lock_) + { + return requests_.ContainsKey(request.RequestID); + } + } + + /// + /// Remvoes an asynchrounous request. + /// + public void EndRequest(Request request) + { + lock (lock_) + { + requests_.Remove(request.RequestID); + } + } + + /// + /// The handle to return with any callbacks. + /// + private object handle_; + + /// + /// The current request options for the subscription. + /// + private int filters_ = (int)TsCDaResultFilter.Minimal; + + /// + /// A table of item identifiers indexed by internal handle. + /// + private ItemTable items_; + + /// + /// A table of autstanding asynchronous requests. + /// + private Hashtable requests_ = new Hashtable(); + private object lock_ = new object(); + + /// + /// Raised when data changed callbacks arrive. + /// + public event TsCDaDataChangedEventHandler DataChangedEvent + { + add { lock (lock_) { _dataChangedEvent += value; } } + remove { lock (lock_) { _dataChangedEvent -= value; } } + } + /// + private event TsCDaDataChangedEventHandler _dataChangedEvent = null; + + /// + /// Called when a data changed event is received. + /// + public void OnDataChange( + int dwTransid, + int hGroup, + int hrMasterquality, + int hrMastererror, + int dwCount, + int[] phClientItems, + object[] pvValues, + short[] pwQualities, + OpcRcw.Da.FILETIME[] pftTimeStamps, + int[] pErrors) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess, true); + try + { + Request request = null; + + lock (lock_) + { + // check for an outstanding request. + if (dwTransid != 0) + { + request = (Request)requests_[dwTransid]; + + + if (request != null) + { + // remove the request. + requests_.Remove(dwTransid); + } + } + + // do nothing if no connections. + if (_dataChangedEvent == null) return; + + // unmarshal item values. + var values = UnmarshalValues( + dwCount, + phClientItems, + pvValues, + pwQualities, + pftTimeStamps, + pErrors); + + // apply request options. + lock (items_) + { + items_.ApplyFilters(filters_ | (int)TsCDaResultFilter.ClientHandle, values); + } + + if (_dataChangedEvent != null) + { + if (!LicenseHandler.IsExpired) + { + // invoke the callback. + _dataChangedEvent(handle_, (request != null) ? request.Handle : null, values); + } + } + } + } + catch (Exception e) + { + var stack = e.StackTrace; + } + } + + // sends read complete notifications. + public void OnReadComplete( + int dwTransid, + int hGroup, + int hrMasterquality, + int hrMastererror, + int dwCount, + int[] phClientItems, + object[] pvValues, + short[] pwQualities, + OpcRcw.Da.FILETIME[] pftTimeStamps, + int[] pErrors) + { + try + { + Request request = null; + TsCDaItemValueResult[] values = null; + + lock (lock_) + { + // do nothing if no outstanding requests. + request = (Request)requests_[dwTransid]; + + if (request == null) + { + return; + } + + // remove the request. + requests_.Remove(dwTransid); + + // unmarshal item values. + values = UnmarshalValues( + dwCount, + phClientItems, + pvValues, + pwQualities, + pftTimeStamps, + pErrors); + + // apply request options. + lock (items_) + { + items_.ApplyFilters(filters_ | (int)TsCDaResultFilter.ClientHandle, values); + } + } + + // end the request. + lock (request) + { + request.EndRequest(values); + } + } + catch (Exception e) + { + var stack = e.StackTrace; + } + } + + // handles asynchronous write complete events. + public void OnWriteComplete( + int dwTransid, + int hGroup, + int hrMastererror, + int dwCount, + int[] phClientItems, + int[] pErrors) + { + try + { + Request request = null; + OpcItemResult[] results = null; + + lock (lock_) + { + // do nothing if no outstanding requests. + request = (Request)requests_[dwTransid]; + + if (request == null) + { + return; + } + + // remove the request. + requests_.Remove(dwTransid); + + // contruct the item results. + results = new OpcItemResult[dwCount]; + + for (var ii = 0; ii < results.Length; ii++) + { + // lookup the external client handle. + var itemID = (OpcItem)items_[phClientItems[ii]]; + + results[ii] = new OpcItemResult(itemID); + results[ii].ClientHandle = phClientItems[ii]; + results[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(pErrors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (pErrors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); } + } + + // apply request options. + lock (items_) + { + items_.ApplyFilters(filters_ | (int)TsCDaResultFilter.ClientHandle, results); + } + } + + // end the request. + lock (request) + { + request.EndRequest(results); + } + } + catch (Exception e) + { + var stack = e.StackTrace; + } + } + + // handles asynchronous request cancel events. + public void OnCancelComplete( + int dwTransid, + int hGroup) + { + try + { + Request request = null; + + lock (lock_) + { + // do nothing if no outstanding requests. + request = (Request)requests_[dwTransid]; + + if (request == null) + { + return; + } + + // remove the request. + requests_.Remove(dwTransid); + } + + // end the request. + lock (request) + { + request.EndRequest(); + } + } + catch (Exception e) + { + var stack = e.StackTrace; + } + } + + /// + /// Creates an array of item value result objects from the callback data. + /// + private TsCDaItemValueResult[] UnmarshalValues( + int dwCount, + int[] phClientItems, + object[] pvValues, + short[] pwQualities, + OpcRcw.Da.FILETIME[] pftTimeStamps, + int[] pErrors) + { + // contruct the item value results. + var values = new TsCDaItemValueResult[dwCount]; + + for (var ii = 0; ii < values.Length; ii++) + { + // lookup the external client handle. + var itemID = (OpcItem)items_[phClientItems[ii]]; + + values[ii] = new TsCDaItemValueResult(itemID); + values[ii].ClientHandle = phClientItems[ii]; + values[ii].Value = pvValues[ii]; + values[ii].Quality = new TsCDaQuality(pwQualities[ii]); + values[ii].QualitySpecified = true; + values[ii].Timestamp = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Interop.Convert(pftTimeStamps[ii])); + values[ii].TimestampSpecified = values[ii].Timestamp != DateTime.MinValue; + values[ii].Result = Technosoftware.DaAeHdaClient.Com.Interop.GetResultID(pErrors[ii]); + values[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (pErrors[ii] == Result.E_BADRIGHTS) { values[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); } + } + + // return results + return values; + } + } + #endregion + } + + #region Request Class + /// + /// Contains the state of an asynchronous request to a COM server. + /// + [Serializable] + internal class Request : TsCDaRequest + { + /// + /// The unique id assigned by the subscription. + /// + internal int RequestID = 0; + + /// + /// The unique id assigned by the server. + /// + internal int CancelID; + + /// + /// The callback used when the request completes. + /// + internal Delegate Callback; + + /// + /// The result filters to use for the request. + /// + internal int Filters; + + /// + /// The set of initial results. + /// + internal OpcItem[] InitialResults; + + /// + /// Initializes the object with a subscription and a unique id. + /// + public Request( + ITsCDaSubscription subscription, + object clientHandle, + int filters, + int requestID, + Delegate callback) + : + base(subscription, clientHandle) + { + Filters = filters; + RequestID = requestID; + Callback = callback; + CancelID = 0; + InitialResults = null; + } + + /// + /// Begins a read request by storing the initial results. + /// + public bool BeginRead(int cancelID, OpcItemResult[] results) + { + CancelID = cancelID; + + // check if results have already arrived. + if (InitialResults != null) + { + if (InitialResults.GetType() == typeof(TsCDaItemValueResult[])) + { + var values = (TsCDaItemValueResult[])InitialResults; + InitialResults = results; + EndRequest(values); + return true; + } + } + + // check that at least one valid item existed. + foreach (var result in results) + { + if (result.Result.Succeeded()) + { + InitialResults = results; + return false; + } + } + + // request complete - all items had errors. + return true; + } + + /// + /// Begins a write request by storing the initial results. + /// + public bool BeginWrite(int cancelID, OpcItemResult[] results) + { + CancelID = cancelID; + + // check if results have already arrived. + if (InitialResults != null) + { + if (InitialResults.GetType() == typeof(OpcItemResult[])) + { + var callbackResults = (OpcItemResult[])InitialResults; + InitialResults = results; + EndRequest(callbackResults); + return true; + } + } + + // check that at least one valid item existed. + foreach (var result in results) + { + if (result.Result.Succeeded()) + { + InitialResults = results; + return false; + } + } + + // apply filters. + for (var ii = 0; ii < results.Length; ii++) + { + if ((Filters & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null; + if ((Filters & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null; + if ((Filters & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null; + } + + + // invoke callback. + ((TsCDaWriteCompleteEventHandler)Callback)(Handle, results); + + return true; + } + + /// + /// Begins a refersh request by saving the cancel id. + /// + public bool BeginRefresh(int cancelID) + { + // save cancel id. + CancelID = cancelID; + + // request not complete. + return false; + } + + /// + /// Completes a read request by processing the values and invoking the callback. + /// + public void EndRequest() + { + // check for cancelled request. + if (typeof(TsCDaCancelCompleteEventHandler).IsInstanceOfType(Callback)) + { + ((TsCDaCancelCompleteEventHandler)Callback)(Handle); + return; + } + } + + /// + /// Completes a read request by processing the values and invoking the callback. + /// + public void EndRequest(TsCDaItemValueResult[] results) + { + // check if the begin request has not completed yet. + if (InitialResults == null) + { + InitialResults = results; + return; + } + + // check for cancelled request. + if (typeof(TsCDaCancelCompleteEventHandler).IsInstanceOfType(Callback)) + { + ((TsCDaCancelCompleteEventHandler)Callback)(Handle); + return; + } + + // apply filters. + for (var ii = 0; ii < results.Length; ii++) + { + if ((Filters & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null; + if ((Filters & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null; + if ((Filters & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null; + + if ((Filters & (int)TsCDaResultFilter.ItemTime) == 0) + { + results[ii].Timestamp = DateTime.MinValue; + results[ii].TimestampSpecified = false; + } + } + + // invoke callback. + if (typeof(TsCDaReadCompleteEventHandler).IsInstanceOfType(Callback)) + { + ((TsCDaReadCompleteEventHandler)Callback)(Handle, results); + } + } + + /// + /// Completes a write request by processing the values and invoking the callback. + /// + public void EndRequest(OpcItemResult[] callbackResults) + { + // check if the begin request has not completed yet. + if (InitialResults == null) + { + InitialResults = callbackResults; + return; + } + + // check for cancelled request. + if (Callback != null && Callback.GetType() == typeof(TsCDaCancelCompleteEventHandler)) + { + ((TsCDaCancelCompleteEventHandler)Callback)(Handle); + return; + } + + // update initial results with callback results. + var results = (OpcItemResult[])InitialResults; + + // insert matching value by checking client handle. + var index = 0; + + for (var ii = 0; ii < results.Length; ii++) + { + while (index < callbackResults.Length) + { + // the initial results have the internal handle stores as the server handle. + if (callbackResults[ii].ServerHandle.Equals(results[index].ServerHandle)) + { + results[index++] = callbackResults[ii]; + break; + } + + index++; + } + } + + // apply filters. + for (var ii = 0; ii < results.Length; ii++) + { + if ((Filters & (int)TsCDaResultFilter.ItemName) == 0) results[ii].ItemName = null; + if ((Filters & (int)TsCDaResultFilter.ItemPath) == 0) results[ii].ItemPath = null; + if ((Filters & (int)TsCDaResultFilter.ClientHandle) == 0) results[ii].ClientHandle = null; + } + + // invoke callback. + if (Callback != null && Callback.GetType() == typeof(TsCDaWriteCompleteEventHandler)) + { + ((TsCDaWriteCompleteEventHandler)Callback)(Handle, results); + } + } + } + #endregion +} diff --git a/Technosoftware/DaAeHdaClient.Com/Da20/Server.cs b/Technosoftware/DaAeHdaClient.Com/Da20/Server.cs new file mode 100644 index 0000000..d7d374e --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Da20/Server.cs @@ -0,0 +1,1649 @@ +#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; +using System.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Da; +using Technosoftware.DaAeHdaClient.Com.Da; +using Technosoftware.DaAeHdaClient.Utilities; + +using Technosoftware.OpcRcw.Da; +using Technosoftware.OpcRcw.Comn; +using Technosoftware.DaAeHdaClient.Com.Utilities; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Da20 +{ + /// + /// An in-process wrapper for a remote OPC Data Access 2.0X server. + /// + internal class Server : Da.Server + { + + #region Constructors + /// + /// The default constructor for the object. + /// + internal Server() { } + + /// + /// Initializes the object with the specified COM server. + /// + internal Server(OpcUrl url, object unknown) : base(url, unknown) { } + #endregion + + #region IDisposable Members + /// + /// This must be called explicitly by clients to ensure the COM server is released. + /// + public new void Dispose() + { + lock (this) + { + if (subscription_ != null) + { + var methodName = "IOPCServer.RemoveGroup"; + try + { + var server = BeginComCall(methodName, true); + server.RemoveGroup(groupHandle_, 0); + } + catch (Exception e) + { + ComCallError(methodName, e); + } + finally + { + EndComCall(methodName); + } + + Utilities.Interop.ReleaseServer(subscription_); + subscription_ = null; + groupHandle_ = 0; + + base.Dispose(); + } + } + } + #endregion + + //====================================================================== + // Connection Management + + /// + /// Connects to the server with the specified URL and credentials. + /// + public override void Initialize(OpcUrl url, OpcConnectData connectData) + { + lock (this) + { + // connect to server. + base.Initialize(url, connectData); + + separators_ = null; + var methodName = "IOPCCommon.GetLocaleID"; + + // create a global subscription required for various item level operations. + var localeID = 0; + try + { + // get the default locale for the server. + var server = BeginComCall(methodName, true); + server.GetLocaleID(out localeID); + } + catch (Exception e) + { + Uninitialize(); + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // create a global subscription required for various item level operations. + methodName = "IOPCServer.AddGroup"; + try + { + // add the subscription. + var iid = typeof(IOPCItemMgt).GUID; + + var server = BeginComCall(methodName, true); + ((IOPCServer)server).AddGroup( + "", + 1, + 500, + 0, + IntPtr.Zero, + IntPtr.Zero, + localeID, + out groupHandle_, + out var revisedUpdateRate, + ref iid, + out subscription_); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + Uninitialize(); + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + + //====================================================================== + // Private Members + + /// + /// A global subscription used for various item level operations. + /// + private object subscription_ = null; + + /// + /// The server handle for the global subscription. + /// + private int groupHandle_ = 0; + + /// + /// True if BROWSE_TO is supported; otherwise false. + /// + private bool browseToSupported_ = true; + + /// + /// A list of seperators used in the browse paths. + /// + private char[] separators_ = null; + private readonly object separatorsLock_ = new(); + + //====================================================================== + // Read + + /// + /// Reads the values for the specified items. + /// + public override TsCDaItemValueResult[] Read(TsCDaItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + // check if nothing to do. + if (items.Length == 0) + { + return Array.Empty(); + } + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // create temporary items. + var temporaryItems = AddItems(items); + var results = new TsCDaItemValueResult[items.Length]; + + try + { + // construct return values. + var cacheItems = new ArrayList(items.Length); + var cacheResults = new ArrayList(items.Length); + var deviceItems = new ArrayList(items.Length); + var deviceResults = new ArrayList(items.Length); + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new TsCDaItemValueResult(temporaryItems[ii]); + + if (temporaryItems[ii].Result.Failed()) + { + results[ii].Result = temporaryItems[ii].Result; + results[ii].DiagnosticInfo = temporaryItems[ii].DiagnosticInfo; + continue; + } + + if (items[ii].MaxAgeSpecified && (items[ii].MaxAge < 0 || items[ii].MaxAge == int.MaxValue)) + { + cacheItems.Add(items[ii]); + cacheResults.Add(results[ii]); + } + else + { + deviceItems.Add(items[ii]); + deviceResults.Add(results[ii]); + } + } + + // read values from the cache. + if (cacheResults.Count > 0) + { + var methodName = "IOPCItemMgt.SetActiveState"; + // items must be active for cache reads. + try + { + // create list of server handles. + var serverHandles = new int[cacheResults.Count]; + + for (var ii = 0; ii < cacheResults.Count; ii++) + { + serverHandles[ii] = (int)((TsCDaItemValueResult)cacheResults[ii]).ServerHandle; + } + + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(subscription_, methodName, true); + subscription.SetActiveState( + cacheResults.Count, + serverHandles, + 1, + out pErrors); + + // free error array. + Marshal.FreeCoTaskMem(pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // read the values. + ReadValues( + (TsCDaItem[])cacheItems.ToArray(typeof(TsCDaItem)), + (TsCDaItemValueResult[])cacheResults.ToArray(typeof(TsCDaItemValueResult)), + true); + } + + // read values from the device. + if (deviceResults.Count > 0) + { + ReadValues( + (TsCDaItem[])deviceItems.ToArray(typeof(TsCDaItem)), + (TsCDaItemValueResult[])deviceResults.ToArray(typeof(TsCDaItemValueResult)), + false); + } + } + + // remove temporary items after read. + finally + { + RemoveItems(temporaryItems); + } + + // return results. + return results; + } + } + + //====================================================================== + // Write + + /// + /// Write the values for the specified items. + /// + public override OpcItemResult[] Write(TsCDaItemValue[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + // check if nothing to do. + if (items.Length == 0) + { + return Array.Empty(); + } + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // create item objects to add temporary items. + var groupItems = new TsCDaItem[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + groupItems[ii] = new TsCDaItem(items[ii]); + } + + // create temporary items. + var results = AddItems(groupItems); + + try + { + // construct list of valid items to write. + var writeItems = new ArrayList(items.Length); + var writeValues = new ArrayList(items.Length); + + for (var ii = 0; ii < items.Length; ii++) + { + if (results[ii].Result.Failed()) + { + continue; + } + + if (items[ii].QualitySpecified || items[ii].TimestampSpecified) + { + results[ii].Result = OpcResult.Da.E_NO_WRITEQT; + results[ii].DiagnosticInfo = null; + continue; + } + + writeItems.Add(results[ii]); + writeValues.Add(items[ii]); + } + + // read values from the cache. + if (writeItems.Count > 0) + { + // initialize input parameters. + var serverHandles = new int[writeItems.Count]; + var values = new object[writeItems.Count]; + + for (var ii = 0; ii < serverHandles.Length; ii++) + { + serverHandles[ii] = (int)((OpcItemResult)writeItems[ii]).ServerHandle; + values[ii] = Utilities.Interop.GetVARIANT(((TsCDaItemValue)writeValues[ii]).Value); + } + + var pErrors = IntPtr.Zero; + + // write item values. + var methodName = "IOPCSyncIO.Write"; + try + { + var subscription = BeginComCall(subscription_, methodName, true); + subscription.Write( + writeItems.Count, + serverHandles, + values, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal results. + var errors = Utilities.Interop.GetInt32s(ref pErrors, writeItems.Count, true); + + for (var ii = 0; ii < writeItems.Count; ii++) + { + var result = (OpcItemResult)writeItems[ii]; + + result.Result = Utilities.Interop.GetResultId(errors[ii]); + result.DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); } + } + } + } + + // remove temporary items + finally + { + RemoveItems(results); + } + + // return results. + return results; + } + } + + //====================================================================== + // Browse + + /// + /// Fetches child elements of the specified branch which match the filter criteria. + /// + public override TsCDaBrowseElement[] Browse( + OpcItem itemId, + TsCDaBrowseFilters filters, + out TsCDaBrowsePosition position) + { + if (filters == null) throw new ArgumentNullException(nameof(filters)); + + position = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + BrowsePosition pos = null; + + var elements = new ArrayList(); + + // search for child branches. + if (filters.BrowseFilter != TsCDaBrowseFilter.Item) + { + var branches = GetElements(elements.Count, itemId, filters, true, ref pos); + + if (branches != null) + { + elements.AddRange(branches); + } + + position = pos; + + // return current set if browse halted. + if (position != null) + { + return (TsCDaBrowseElement[])elements.ToArray(typeof(TsCDaBrowseElement)); + } + } + + // search for child items. + if (filters.BrowseFilter != TsCDaBrowseFilter.Branch) + { + var items = GetElements(elements.Count, itemId, filters, false, ref pos); + + if (items != null) + { + elements.AddRange(items); + } + + position = pos; + } + + // return the elements. + return (TsCDaBrowseElement[])elements.ToArray(typeof(TsCDaBrowseElement)); + } + } + + //====================================================================== + // BrowseNext + + /// + /// Continues a browse operation with previously specified search criteria. + /// + public override TsCDaBrowseElement[] BrowseNext(ref TsCDaBrowsePosition position) + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // check for valid browse position object. + if (position == null && position.GetType() != typeof(BrowsePosition)) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + var pos = (BrowsePosition)position; + + var itemID = pos.ItemID; + var filters = pos.Filters; + + var elements = new ArrayList(); + + // search for child branches. + if (pos.IsBranch) + { + var branches = GetElements(elements.Count, itemID, filters, true, ref pos); + + if (branches != null) + { + elements.AddRange(branches); + } + + position = pos; + + // return current set if browse halted. + if (position != null) + { + return (TsCDaBrowseElement[])elements.ToArray(typeof(TsCDaBrowseElement)); + } + } + + // search for child items. + if (filters.BrowseFilter != TsCDaBrowseFilter.Branch) + { + var items = GetElements(elements.Count, itemID, filters, false, ref pos); + + if (items != null) + { + elements.AddRange(items); + } + + position = pos; + } + + // return the elements. + return (TsCDaBrowseElement[])elements.ToArray(typeof(TsCDaBrowseElement)); + } + } + + //====================================================================== + // GetProperties + + /// + /// Returns the specified properties for a set of items. + /// + public override TsCDaItemPropertyCollection[] GetProperties( + OpcItem[] itemIds, + TsDaPropertyID[] propertyIDs, + bool returnValues) + { + if (itemIds == null) throw new ArgumentNullException(nameof(itemIds)); + + // check for trival case. + if (itemIds.Length == 0) + { + return Array.Empty(); + } + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // initialize list of property lists. + var propertyLists = new TsCDaItemPropertyCollection[itemIds.Length]; + + for (var ii = 0; ii < itemIds.Length; ii++) + { + propertyLists[ii] = new TsCDaItemPropertyCollection + { + ItemName = itemIds[ii].ItemName, + ItemPath = itemIds[ii].ItemPath + }; + + // fetch properties for item. + try + { + var properties = GetProperties(itemIds[ii].ItemName, propertyIDs, returnValues); + + if (properties != null) + { + propertyLists[ii].AddRange(properties); + } + + propertyLists[ii].Result = OpcResult.S_OK; + } + catch (OpcResultException e) + { + propertyLists[ii].Result = e.Result; + } + catch (Exception e) + { + propertyLists[ii].Result = new OpcResult(Marshal.GetHRForException(e)); + } + } + + // return property lists. + return propertyLists; + } + } + + //====================================================================== + // Private Methods + + /// + /// Adds a set of temporary items used for a read/write operation. + /// + private OpcItemResult[] AddItems(TsCDaItem[] items) + { + // add items to subscription. + var count = items.Length; + + var definitions = Technosoftware.DaAeHdaClient.Com.Da.Interop.GetOPCITEMDEFs(items); + + // ensure all items are created as inactive. + for (var ii = 0; ii < definitions.Length; ii++) + { + definitions[ii].bActive = 0; + } + + // initialize output parameters. + var pResults = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // get the default current for the server. + ((IOPCCommon)server_).GetLocaleID(out var localeID); + + var hLocale = GCHandle.Alloc(localeID, GCHandleType.Pinned); + + var methodName = "IOPCGroupStateMgt.SetState"; + try + { + // ensure the current locale is correct. + var subscription = BeginComCall(subscription_, methodName, true); + ((IOPCGroupStateMgt)subscription).SetState( + IntPtr.Zero, + out var updateRate, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + hLocale.AddrOfPinnedObject(), + IntPtr.Zero); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + if (hLocale.IsAllocated) hLocale.Free(); + EndComCall(methodName); + } + + // add items to subscription. + methodName = "IOPCItemMgt.AddItems"; + try + { + var subscription = BeginComCall(subscription_, methodName, true); + subscription.AddItems( + count, + definitions, + out pResults, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + if (hLocale.IsAllocated) hLocale.Free(); + } + + // unmarshal output parameters. + var serverHandles = Technosoftware.DaAeHdaClient.Com.Da.Interop.GetItemResults(ref pResults, count, true); + var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true); + + // create results list. + var results = new OpcItemResult[count]; + + for (var ii = 0; ii < count; ii++) + { + results[ii] = new OpcItemResult(items[ii]) + { + ServerHandle = null, + Result = Utilities.Interop.GetResultId(errors[ii]), + DiagnosticInfo = null + }; + + if (results[ii].Result.Succeeded()) + { + results[ii].ServerHandle = serverHandles[ii]; + } + } + + // return results. + return results; + } + + /// + /// Removes a set of temporary items used for a read/write operation. + /// + private void RemoveItems(OpcItemResult[] items) + { + try + { + // contruct array of valid server handles. + var handles = new ArrayList(items.Length); + + foreach (var item in items) + { + if (item.Result.Succeeded() && item.ServerHandle.GetType() == typeof(int)) + { + handles.Add((int)item.ServerHandle); + } + } + + // check if nothing to do. + if (handles.Count == 0) + { + return; + } + + // remove items from server. + var pErrors = IntPtr.Zero; + + var methodName = "IOPCItemMgt.RemoveItems"; + try + { + var subscription = BeginComCall(subscription_, methodName, true); + ((IOPCItemMgt)subscription).RemoveItems( + handles.Count, + (int[])handles.ToArray(typeof(int)), + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + // free returned error array. + Utilities.Interop.GetInt32s(ref pErrors, handles.Count, true); + } + + } + catch + { + // ignore errors. + } + } + + /// + /// Reads a set of values. + /// + private void ReadValues(TsCDaItem[] items, TsCDaItemValueResult[] results, bool cache) + { + if (items.Length == 0 || results.Length == 0) return; + + // marshal input parameters. + var serverHandles = new int[results.Length]; + + for (var ii = 0; ii < results.Length; ii++) + { + serverHandles[ii] = Convert.ToInt32(results[ii].ServerHandle); + } + + // initialize output parameters. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + var methodName = "IOPCSyncIO.Read"; + try + { + var subscription = BeginComCall(subscription_, methodName, true); + subscription.Read( + (cache) ? OPCDATASOURCE.OPC_DS_CACHE : OPCDATASOURCE.OPC_DS_DEVICE, + results.Length, + serverHandles, + out pValues, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + // free returned error array. + } + + // unmarshal output parameters. + var values = Technosoftware.DaAeHdaClient.Com.Da.Interop.GetItemValues(ref pValues, results.Length, true); + var errors = Utilities.Interop.GetInt32s(ref pErrors, results.Length, true); + + // pre-fetch the current locale to use for data conversions. + GetLocale(); + + // construct results list. + for (var ii = 0; ii < results.Length; ii++) + { + results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + results[ii].DiagnosticInfo = null; + + if (results[ii].Result.Succeeded()) + { + results[ii].Value = values[ii].Value; + results[ii].Quality = values[ii].Quality; + results[ii].QualitySpecified = values[ii].QualitySpecified; + results[ii].Timestamp = values[ii].Timestamp; + results[ii].TimestampSpecified = values[ii].TimestampSpecified; + } + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); } + + // convert the data type since the server does not support the feature. + if (results[ii].Value != null && items[ii].ReqType != null) + { + try + { + results[ii].Value = ChangeType(results[ii].Value, items[ii].ReqType, "en-US"); + } + catch (Exception e) + { + results[ii].Value = null; + results[ii].Quality = TsCDaQuality.Bad; + results[ii].QualitySpecified = true; + results[ii].Timestamp = DateTime.MinValue; + results[ii].TimestampSpecified = false; + + if (e.GetType() == typeof(OverflowException)) + { + results[ii].Result = Utilities.Interop.GetResultId(Result.E_RANGE); + } + else + { + results[ii].Result = Utilities.Interop.GetResultId(Result.E_BADTYPE); + } + } + } + } + } + + /// + /// Returns the set of available properties for the item. + /// + private TsCDaItemProperty[] GetAvailableProperties(string itemID) + { + // validate argument. + if (itemID == null || itemID.Length == 0) + { + throw new OpcResultException(OpcResult.Da.E_INVALID_ITEM_NAME); + } + + // query for available properties. + var count = 0; + + var pPropertyIDs = IntPtr.Zero; + var pDescriptions = IntPtr.Zero; + var pDataTypes = IntPtr.Zero; + + var methodName = "IOPCItemProperties.QueryAvailableProperties"; + try + { + var server = BeginComCall(methodName, true); + server.QueryAvailableProperties( + itemID, + out count, + out pPropertyIDs, + out pDescriptions, + out pDataTypes); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + } + catch (Exception e) + { + ComCallError(methodName, e); + throw new OpcResultException(OpcResult.Da.E_UNKNOWN_ITEM_NAME); + } + finally + { + EndComCall(methodName); + // free returned error array. + } + + // unmarshal results. + var propertyIDs = Utilities.Interop.GetInt32s(ref pPropertyIDs, count, true); + var datatypes = Utilities.Interop.GetInt16s(ref pDataTypes, count, true); + var descriptions = Utilities.Interop.GetUnicodeStrings(ref pDescriptions, count, true); + + // check for error condition. + if (count == 0) + { + return null; + } + + // initialize property objects. + var properties = new TsCDaItemProperty[count]; + + for (var ii = 0; ii < count; ii++) + { + properties[ii] = new TsCDaItemProperty + { + ID = Technosoftware.DaAeHdaClient.Com.Da.Interop.GetPropertyID(propertyIDs[ii]), + Description = descriptions[ii], + DataType = Utilities.Interop.GetType((VarEnum)datatypes[ii]), + ItemName = null, + ItemPath = null, + Result = OpcResult.S_OK, + Value = null + }; + } + + // return property list. + return properties; + } + + /// + /// Fetches the property item id for the specified set of properties. + /// + private void GetItemIDs(string itemID, TsCDaItemProperty[] properties) + { + try + { + // create input arguments; + var propertyIDs = new int[properties.Length]; + + for (var ii = 0; ii < properties.Length; ii++) + { + propertyIDs[ii] = properties[ii].ID.Code; + } + + // lookup item ids. + var pItemIDs = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + ((IOPCItemProperties)server_).LookupItemIDs( + itemID, + properties.Length, + propertyIDs, + out pItemIDs, + out pErrors); + + // unmarshal results. + var itemIDs = Utilities.Interop.GetUnicodeStrings(ref pItemIDs, properties.Length, true); + var errors = Utilities.Interop.GetInt32s(ref pErrors, properties.Length, true); + + // update property objects. + for (var ii = 0; ii < properties.Length; ii++) + { + properties[ii].ItemName = null; + properties[ii].ItemPath = null; + + if (errors[ii] >= 0) + { + properties[ii].ItemName = itemIDs[ii]; + } + } + } + catch (Exception) + { + // set item ids to null for all properties. + foreach (var property in properties) + { + property.ItemName = null; + property.ItemPath = null; + } + } + } + + /// + /// Fetches the property values for the specified set of properties. + /// + private void GetValues(string itemID, TsCDaItemProperty[] properties) + { + try + { + // create input arguments; + var propertyIDs = new int[properties.Length]; + + for (var ii = 0; ii < properties.Length; ii++) + { + propertyIDs[ii] = properties[ii].ID.Code; + } + + // lookup item ids. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + ((IOPCItemProperties)server_).GetItemProperties( + itemID, + properties.Length, + propertyIDs, + out pValues, + out pErrors); + + // unmarshal results. + var values = Interop.GetVARIANTs(ref pValues, properties.Length, true); + var errors = Utilities.Interop.GetInt32s(ref pErrors, properties.Length, true); + + // update property objects. + for (var ii = 0; ii < properties.Length; ii++) + { + properties[ii].Value = null; + + // ignore value for invalid properties. + if (!properties[ii].Result.Succeeded()) + { + continue; + } + + properties[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + + // substitute property reult code. + if (errors[ii] == Result.E_BADRIGHTS) + { + properties[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); + } + + if (properties[ii].Result.Succeeded()) + { + properties[ii].Value = Technosoftware.DaAeHdaClient.Com.Da.Interop.UnmarshalPropertyValue(properties[ii].ID, values[ii]); + } + } + } + catch (Exception e) + { + // set general error code as the result for each property. + var result = new OpcResult(Marshal.GetHRForException(e)); + + foreach (var property in properties) + { + property.Value = null; + property.Result = result; + } + } + } + + /// + /// Gets the specified properties for the specified item. + /// + private TsCDaItemProperty[] GetProperties(string itemID, TsDaPropertyID[] propertyIDs, bool returnValues) + { + TsCDaItemProperty[] properties; + + // return all available properties. + if (propertyIDs == null) + { + properties = GetAvailableProperties(itemID); + } + + // return on the selected properties. + else + { + // get available properties. + var availableProperties = GetAvailableProperties(itemID); + + // initialize result list. + properties = new TsCDaItemProperty[propertyIDs.Length]; + + for (var ii = 0; ii < propertyIDs.Length; ii++) + { + // search available property list for specified property. + foreach (var property in availableProperties) + { + if (property.ID == propertyIDs[ii]) + { + properties[ii] = (TsCDaItemProperty)property.Clone(); + properties[ii].ID = propertyIDs[ii]; + break; + } + } + + // property not valid for the item. + if (properties[ii] == null) + { + properties[ii] = new TsCDaItemProperty + { + ID = propertyIDs[ii], + Result = OpcResult.Da.E_INVALID_PID + }; + } + } + } + + // fill in missing fields in property objects. + if (properties != null) + { + GetItemIDs(itemID, properties); + + if (returnValues) + { + GetValues(itemID, properties); + } + } + + // return property list. + return properties; + } + + + /// + /// Returns an enumerator for the children of the specified branch. + /// + private EnumString GetEnumerator(string itemID, TsCDaBrowseFilters filters, bool branches, bool flat) + { + var browser = (IOPCBrowseServerAddressSpace)server_; + + if (!flat) + { + if (itemID == null) + { + if (browseToSupported_) + { + // move to the root of the hierarchial address spaces. + try + { + var id = string.Empty; + browser.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_TO, id); + } + catch (Exception e) + { + var message = string.Format("ChangeBrowsePosition to root with BROWSE_TO={0} failed with error {1}. BROWSE_TO not supported.", string.Empty, e.Message); + Utils.Trace(e, message); + browseToSupported_ = false; + } + } + if (!browseToSupported_) + { + // browse to root. + while (true) + { + try + { + browser.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, string.Empty); + } + catch (Exception) + { + break; + } + } + } + } + else + { + // move to the specified branch for hierarchial address spaces. + var id = itemID ?? ""; + if (browseToSupported_) + { + try + { + browser.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_TO, id); + } + catch (Exception) + { + browseToSupported_ = false; + } + } + if (!browseToSupported_) + { + // try to browse down instead. + try + { + browser.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_DOWN, id); + } + catch (Exception) + { + + // browse to root. + while (true) + { + try + { + browser.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, string.Empty); + } + catch (Exception) + { + break; + } + } + + // parse the browse path. + string[] paths = null; + + lock (separatorsLock_) + { + if (separators_ != null) + { + paths = id.Split(separators_); + } + else + { + paths = id.Split(separators_); + } + } + + // browse to correct location. + for (var ii = 0; ii < paths.Length; ii++) + { + if (paths[ii] == null || paths[ii].Length == 0) + { + continue; + } + + try + { + browser.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_DOWN, paths[ii]); + } + catch (Exception) + { + throw new OpcResultException(OpcResult.Da.E_UNKNOWN_ITEM_NAME); + } + } + } + } + } + } + + try + { + // create the enumerator. + var browseType = (branches) ? OPCBROWSETYPE.OPC_BRANCH : OPCBROWSETYPE.OPC_LEAF; + + if (flat) + { + browseType = OPCBROWSETYPE.OPC_FLAT; + } + + browser.BrowseOPCItemIDs( + browseType, + filters.ElementNameFilter ?? "", + (short)VarEnum.VT_EMPTY, + 0, + out var enumerator); + + // return the enumerator. + return new EnumString(enumerator); + } + catch (Exception) + { + throw new OpcResultException(OpcResult.Da.E_UNKNOWN_ITEM_NAME); + } + } + + + /// + /// Detects the separators used in the item id. + /// + private void DetectAndSaveSeparators(string browseName, string itemID) + { + if (!itemID.EndsWith(browseName)) + { + return; + } + + if (string.Compare(itemID, browseName, true) == 0) + { + return; + } + + var separator = itemID[itemID.Length - browseName.Length - 1]; + + lock (separatorsLock_) + { + var index = -1; + + if (separators_ != null) + { + for (var ii = 0; ii < separators_.Length; ii++) + { + if (separators_[ii] == separator) + { + index = ii; + break; + } + } + + if (index == -1) + { + var separators = new char[separators_.Length + 1]; + Array.Copy(separators_, separators, separators_.Length); + separators_ = separators; + } + } + + if (index == -1) + { + separators_ ??= new char[1]; + + separators_[separators_.Length - 1] = separator; + } + } + } + + /// + /// Reads a single value from the enumerator and returns a browse element. + /// + private TsCDaBrowseElement GetElement( + OpcItem itemID, + string name, + TsCDaBrowseFilters filters, + bool isBranch) + { + if (name == null) + { + return null; + } + + var element = new TsCDaBrowseElement + { + Name = name, + HasChildren = isBranch, + ItemPath = null + }; + + // get item id. + try + { + ((IOPCBrowseServerAddressSpace)server_).GetItemID(element.Name, out var itemName); + element.ItemName = itemName; + + // detect separator. + if (element.ItemName != null) + { + DetectAndSaveSeparators(element.Name, element.ItemName); + } + } + + // this is an error that should not occur. + catch + { + element.ItemName = name; + } + + // check if element is an actual item or just a branch. + try + { + var definition = new OPCITEMDEF + { + szItemID = element.ItemName, + szAccessPath = null, + hClient = 0, + bActive = 0, + vtRequestedDataType = (short)VarEnum.VT_EMPTY, + dwBlobSize = 0, + pBlob = IntPtr.Zero + }; + + var pResults = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // validate item. + ((IOPCItemMgt)subscription_).ValidateItems( + 1, + new OPCITEMDEF[] { definition }, + 0, + out pResults, + out pErrors); + + // free results. + Technosoftware.DaAeHdaClient.Com.Da.Interop.GetItemResults(ref pResults, 1, true); + + var errors = Utilities.Interop.GetInt32s(ref pErrors, 1, true); + + // can only be an item if validation succeeded. + element.IsItem = (errors[0] >= 0); + } + + // this is an error that should not occur - must be a branch. + catch + { + element.IsItem = false; + // Because ABB Real-TPI server always return ItemName == null we use Name instead to fix browsing problem + element.ItemName = element.Name; + } + + + // fetch item properties. + try + { + if (filters.ReturnAllProperties) + { + element.Properties = GetProperties(element.ItemName, null, filters.ReturnPropertyValues); + } + else if (filters.PropertyIDs != null) + { + element.Properties = GetProperties(element.ItemName, filters.PropertyIDs, filters.ReturnPropertyValues); + } + } + + // return no properties if an error fetching properties occurred. + catch + { + element.Properties = null; + } + + // return new element. + return element; + } + + /// + /// Returns a list of child elements that meet the filter criteria. + /// + private TsCDaBrowseElement[] GetElements( + int elementsFound, + OpcItem itemID, + TsCDaBrowseFilters filters, + bool branches, + ref BrowsePosition position) + { + // get the enumerator. + EnumString enumerator; + if (position == null) + { + var browser = (IOPCBrowseServerAddressSpace)server_; + + // check the server address space type. + OPCNAMESPACETYPE namespaceType; + try + { + browser.QueryOrganization(out namespaceType); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCBrowseServerAddressSpace.QueryOrganization", e); + } + + // return an empty list if requesting branches for a flat address space. + if (namespaceType == OPCNAMESPACETYPE.OPC_NS_FLAT) + { + if (branches) + { + return Array.Empty(); + } + + // check that root is browsed for flat address spaces. + if (itemID != null && itemID.ItemName != null && itemID.ItemName.Length > 0) + { + throw new OpcResultException(OpcResult.Da.E_UNKNOWN_ITEM_NAME); + } + } + + // get the enumerator. + enumerator = GetEnumerator( + itemID?.ItemName, + filters, + branches, + namespaceType == OPCNAMESPACETYPE.OPC_NS_FLAT); + } + else + { + enumerator = position.Enumerator; + } + + var elements = new ArrayList(); + var start = 0; + string[] names = null; + + // get cached name list. + if (position != null) + { + start = position.Index; + names = position.Names; + position = null; + } + + do + { + if (names != null) + { + for (var ii = start; ii < names.Length; ii++) + { + // check if max returned elements is exceeded. + if (filters.MaxElementsReturned != 0 && filters.MaxElementsReturned == elements.Count + elementsFound) + { + position = new BrowsePosition(itemID, filters, enumerator, branches) + { + Names = names, + Index = ii + }; + break; + } + + // read elements one at a time. + // get next element. + var element = GetElement(itemID, names[ii], filters, branches); + if (element == null) + { + break; + } + + // add element. + elements.Add(element); + } + } + + // check if browse halted. + if (position != null) + { + break; + } + + // fetch next element name. + names = enumerator.Next(10); + start = 0; + } + while (names != null && names.Length > 0); + + // free enumerator. + if (position == null) + { + enumerator.Dispose(); + } + + // return list of elements. + return (TsCDaBrowseElement[])elements.ToArray(typeof(TsCDaBrowseElement)); + } + + //====================================================================== + // Private Methods + + /// + /// Creates a new instance of a subscription. + /// + protected override Technosoftware.DaAeHdaClient.Com.Da.Subscription CreateSubscription( + object group, + TsCDaSubscriptionState state, + int filters) + { + return new Subscription(group, state, filters); + } + } + + /// + /// Implements an object that handles multi-step browse operations for DA2.05 servers. + /// + [Serializable] + internal class BrowsePosition : TsCDaBrowsePosition + { + /// + /// The enumerator for a browse operation. + /// + internal EnumString Enumerator = null; + + /// + /// Whether the current enumerator returns branches or leaves. + /// + internal bool IsBranch = true; + + /// + /// The pre-fetched set of names. + /// + internal string[] Names = null; + + /// + /// The current index in the pre-fetched names. + /// + internal int Index = 0; + + /// + /// Initializes a browse position + /// + internal BrowsePosition( + OpcItem itemID, + TsCDaBrowseFilters filters, + EnumString enumerator, + bool isBranch) + : + base(itemID, filters) + { + Enumerator = enumerator; + IsBranch = isBranch; + } + + /// + /// Releases unmanaged resources held by the object. + /// + public override void Dispose() + { + if (Enumerator != null) + { + Enumerator.Dispose(); + Enumerator = null; + } + } + + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + var clone = (BrowsePosition)MemberwiseClone(); + clone.Enumerator = Enumerator.Clone(); + return clone; + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Da20/Subscription.cs b/Technosoftware/DaAeHdaClient.Com/Da20/Subscription.cs new file mode 100644 index 0000000..ee8f561 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Da20/Subscription.cs @@ -0,0 +1,586 @@ +#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; + +using Technosoftware.DaAeHdaClient.Da; +using Technosoftware.DaAeHdaClient.Com.Da; +using Technosoftware.DaAeHdaClient.Utilities; + +using Technosoftware.OpcRcw.Da; +using Technosoftware.DaAeHdaClient.Com.Utilities; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Da20 +{ + /// + /// An in-process wrapper for a remote OPC Data Access 2.0X subscription. + /// + internal class Subscription : Technosoftware.DaAeHdaClient.Com.Da.Subscription + { + #region Constructors + /// + /// Initializes a new instance of a subscription. + /// + internal Subscription(object subscription, TsCDaSubscriptionState state, int filters) : + base(subscription, state, filters) + { + } + #endregion + + #region ISubscription Members + /// + /// Returns the current state of the subscription. + /// + /// The current state of the subscription. + public override TsCDaSubscriptionState GetState() + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCGroupStateMgt.GetState"; + var state = new TsCDaSubscriptionState { ClientHandle = _handle }; + + string name = null; + + try + { + var active = 0; + var updateRate = 0; + float deadband = 0; + var timebias = 0; + var localeID = 0; + var clientHandle = 0; + var serverHandle = 0; + + var subscription = BeginComCall(methodName, true); + subscription.GetState( + out updateRate, + out active, + out name, + out timebias, + out deadband, + out localeID, + out clientHandle, + out serverHandle); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + state.Name = name; + state.ServerHandle = serverHandle; + state.Active = active != 0; + state.UpdateRate = updateRate; + state.TimeBias = timebias; + state.Deadband = deadband; + state.Locale = Technosoftware.DaAeHdaClient.Com.Interop.GetLocale(localeID); + + // cache the name separately. + name_ = state.Name; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Technosoftware.DaAeHdaClient.Com.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + state.KeepAlive = 0; + + return state; + } + } + + /// + /// Tells the server to send an data change update for all subscription items containing the cached values. + /// + public override void Refresh() + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCAsyncIO2.Refresh2"; + try + { + var cancelID = 0; + var subscription = BeginComCall(methodName, true); + subscription.Refresh2(OPCDATASOURCE.OPC_DS_CACHE, ++_counter, out cancelID); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + + /// + /// Sets whether data change callbacks are enabled. + /// + public override void SetEnabled(bool enabled) + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCAsyncIO2.SetEnable"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.SetEnable((enabled) ? 1 : 0); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + + /// + /// Gets whether data change callbacks are enabled. + /// + public override bool GetEnabled() + { + if (subscription_ == null) throw new NotConnectedException(); + lock (lock_) + { + var methodName = "IOPCAsyncIO2.GetEnable"; + try + { + var enabled = 0; + var subscription = BeginComCall(methodName, true); + subscription.GetEnable(out enabled); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + return enabled != 0; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + } + #endregion + + #region Private and Protected Members + /// + /// Reads a set of items using DA2.0 interfaces. + /// + protected override TsCDaItemValueResult[] Read(OpcItem[] itemIDs, TsCDaItem[] items) + { + if (subscription_ == null) throw new NotConnectedException(); + // create result list. + var results = new TsCDaItemValueResult[itemIDs.Length]; + + // separate into cache reads and device reads. + var cacheReads = new ArrayList(); + var deviceReads = new ArrayList(); + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new TsCDaItemValueResult(itemIDs[ii]); + + if (items[ii].MaxAgeSpecified && (items[ii].MaxAge < 0 || items[ii].MaxAge == int.MaxValue)) + { + cacheReads.Add(results[ii]); + } + else + { + deviceReads.Add(results[ii]); + } + } + + // read items from cache. + if (cacheReads.Count > 0) + { + Read((TsCDaItemValueResult[])cacheReads.ToArray(typeof(TsCDaItemValueResult)), true); + } + + // read items from device. + if (deviceReads.Count > 0) + { + Read((TsCDaItemValueResult[])deviceReads.ToArray(typeof(TsCDaItemValueResult)), false); + } + + // return results. + return results; + } + + /// + /// Reads a set of values. + /// + private void Read(TsCDaItemValueResult[] items, bool cache) + { + if (items.Length == 0) return; + + // marshal input parameters. + var serverHandles = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + serverHandles[ii] = (int)items[ii].ServerHandle; + } + + // initialize output parameters. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + var methodName = "IOPCSyncIO.Read"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.Read( + (cache) ? OPCDATASOURCE.OPC_DS_CACHE : OPCDATASOURCE.OPC_DS_DEVICE, + items.Length, + serverHandles, + out pValues, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal output parameters. + var values = Technosoftware.DaAeHdaClient.Com.Da.Interop.GetItemValues(ref pValues, items.Length, true); + var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); + + // construct results list. + for (var ii = 0; ii < items.Length; ii++) + { + items[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + items[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { items[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); } + + if (items[ii].Result.Succeeded()) + { + items[ii].Value = values[ii].Value; + items[ii].Quality = values[ii].Quality; + items[ii].QualitySpecified = values[ii].QualitySpecified; + items[ii].Timestamp = values[ii].Timestamp; + items[ii].TimestampSpecified = values[ii].TimestampSpecified; + } + } + } + + /// + /// Writes a set of items using DA2.0 interfaces. + /// + protected override OpcItemResult[] Write(OpcItem[] itemIDs, TsCDaItemValue[] items) + { + if (subscription_ == null) throw new NotConnectedException(); + // create result list. + var results = new OpcItemResult[itemIDs.Length]; + + // construct list of valid items to write. + var writeItems = new ArrayList(itemIDs.Length); + var writeValues = new ArrayList(itemIDs.Length); + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(itemIDs[ii]); + + if (items[ii].QualitySpecified || items[ii].TimestampSpecified) + { + results[ii].Result = OpcResult.Da.E_NO_WRITEQT; + results[ii].DiagnosticInfo = null; + continue; + } + + writeItems.Add(results[ii]); + writeValues.Add(items[ii]); + } + + // check if there is nothing to do. + if (writeItems.Count == 0) + { + return results; + } + + // initialize input parameters. + var serverHandles = new int[writeItems.Count]; + var values = new object[writeItems.Count]; + + for (var ii = 0; ii < serverHandles.Length; ii++) + { + serverHandles[ii] = (int)((OpcItemResult)writeItems[ii]).ServerHandle; + values[ii] = Utilities.Interop.GetVARIANT(((TsCDaItemValue)writeValues[ii]).Value); + } + + var pErrors = IntPtr.Zero; + + // write item values. + var methodName = "IOPCSyncIO.Write"; + try + { + var subscription = BeginComCall(methodName, true); + subscription.Write( + writeItems.Count, + serverHandles, + values, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // unmarshal results. + var errors = Utilities.Interop.GetInt32s(ref pErrors, writeItems.Count, true); + + for (var ii = 0; ii < writeItems.Count; ii++) + { + var result = (OpcItemResult)writeItems[ii]; + + result.Result = Utilities.Interop.GetResultId(errors[ii]); + result.DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); } + } + + // return results. + return results; + } + + /// + /// Begins an asynchronous read of a set of items using DA2.0 interfaces. + /// + protected override OpcItemResult[] BeginRead( + OpcItem[] itemIDs, + TsCDaItem[] items, + int requestID, + out int cancelID) + { + var methodName = "IOPCAsyncIO2.Read"; + try + { + // marshal input parameters. + var serverHandles = new int[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + serverHandles[ii] = (int)itemIDs[ii].ServerHandle; + } + + // initialize output parameters. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.Read( + itemIDs.Length, + serverHandles, + requestID, + out cancelID, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // unmarshal output parameters. + var errors = Utilities.Interop.GetInt32s(ref pErrors, itemIDs.Length, true); + + // create item results. + var results = new OpcItemResult[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new OpcItemResult(itemIDs[ii]); + results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + results[ii].DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_WRITEONLY, Result.E_BADRIGHTS); } + } + + // return results. + return results; + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + } + + /// + /// Begins an asynchronous write for a set of items using DA2.0 interfaces. + /// + protected override OpcItemResult[] BeginWrite( + OpcItem[] itemIDs, + TsCDaItemValue[] items, + int requestID, + out int cancelID) + { + cancelID = 0; + + var validItems = new ArrayList(); + var validValues = new ArrayList(); + + // construct initial result list. + var results = new OpcItemResult[itemIDs.Length]; + + for (var ii = 0; ii < itemIDs.Length; ii++) + { + results[ii] = new OpcItemResult(itemIDs[ii]); + + results[ii].Result = OpcResult.S_OK; + results[ii].DiagnosticInfo = null; + + if (items[ii].QualitySpecified || items[ii].TimestampSpecified) + { + results[ii].Result = OpcResult.Da.E_NO_WRITEQT; + results[ii].DiagnosticInfo = null; + continue; + } + + validItems.Add(results[ii]); + validValues.Add(Utilities.Interop.GetVARIANT(items[ii].Value)); + } + + // check if any valid items exist. + if (validItems.Count == 0) + { + return results; + } + + var methodName = "IOPCAsyncIO2.Write"; + try + { + // initialize input parameters. + var serverHandles = new int[validItems.Count]; + + for (var ii = 0; ii < validItems.Count; ii++) + { + serverHandles[ii] = (int)((OpcItemResult)validItems[ii]).ServerHandle; + } + + // write to sever. + var pErrors = IntPtr.Zero; + + var subscription = BeginComCall(methodName, true); + subscription.Write( + validItems.Count, + serverHandles, + (object[])validValues.ToArray(typeof(object)), + requestID, + out cancelID, + out pErrors); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new Exception($"{methodName} call was cancelled due to response timeout"); + } + + // unmarshal results. + var errors = Utilities.Interop.GetInt32s(ref pErrors, validItems.Count, true); + + // create result list. + for (var ii = 0; ii < validItems.Count; ii++) + { + var result = (OpcItemResult)validItems[ii]; + + result.Result = Utilities.Interop.GetResultId(errors[ii]); + result.DiagnosticInfo = null; + + // convert COM code to unified DA code. + if (errors[ii] == Result.E_BADRIGHTS) { results[ii].Result = new OpcResult(OpcResult.Da.E_READONLY, Result.E_BADRIGHTS); } + } + } + catch (Exception e) + { + ComCallError(methodName, e); + throw Utilities.Interop.CreateException(methodName, e); + } + finally + { + EndComCall(methodName); + } + + // return results. + return results; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/EnumString.cs b/Technosoftware/DaAeHdaClient.Com/EnumString.cs new file mode 100644 index 0000000..cfcd095 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/EnumString.cs @@ -0,0 +1,128 @@ +#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.Runtime.InteropServices; +using Technosoftware.OpcRcw.Comn; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// A wrapper for the COM IEnumString interface. + /// + internal class EnumString : IDisposable + { + /// + /// A reference to the remote COM object. + /// + private IEnumString m_enumerator = null; + + /// + /// Initializes the object with an enumerator. + /// + public EnumString(object enumerator) + { + m_enumerator = (IEnumString)enumerator; + } + + /// + /// Releases the remote COM object. + /// + public void Dispose() + { + Utilities.Interop.ReleaseServer(m_enumerator); + m_enumerator = null; + } + + //===================================================================== + // IEnumString + + /// + /// Fetches the next subscription of strings. + /// + public string[] Next(int count) + { + try + { + // create buffer. + var buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr))*count); + + try + { + // fetch next subscription of strings. + var fetched = 0; + + m_enumerator.RemoteNext( + count, + buffer, + out fetched); + + // return empty array if at end of list. + if (fetched == 0) + { + return new string[0]; + } + + return Interop.GetUnicodeStrings(ref buffer, fetched, true); + } + finally + { + Marshal.FreeCoTaskMem(buffer); + } + } + + // return null on any error. + catch (Exception) + { + return null; + } + } + + /// + /// Skips a number of strings. + /// + public void Skip(int count) + { + m_enumerator.Skip(count); + } + + /// + /// Sets pointer to the start of the list. + /// + public void Reset() + { + m_enumerator.Reset(); + } + + /// + /// Clones the enumerator. + /// + public EnumString Clone() + { + IEnumString enumerator; + m_enumerator.Clone(out enumerator); + return new EnumString(enumerator); + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/FILETIME.cs b/Technosoftware/DaAeHdaClient.Com/FILETIME.cs new file mode 100644 index 0000000..e31f6a8 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/FILETIME.cs @@ -0,0 +1,44 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// The COM FILETIME structure. + /// + public struct FILETIME + { + /// + /// The least significant DWORD. + /// + public int dwLowDateTime; + + /// + /// The most significant DWORD. + /// + public int dwHighDateTime; + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Factory.cs b/Technosoftware/DaAeHdaClient.Com/Factory.cs new file mode 100644 index 0000000..358664c --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Factory.cs @@ -0,0 +1,290 @@ +#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.Xml; +using System.Net; +using System.Text; +using System.Collections; +using System.Globalization; +using System.Runtime.Serialization; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// The default class used to instantiate server objects. + /// + [Serializable] + public class Factory : OpcFactory + { + //====================================================================== + // Construction + + /// + /// Initializes an instance for use for in process objects. + /// + public Factory() : base(null) + { + // do nothing. + } + + //====================================================================== + // ISerializable + + /// + /// Contructs a server by de-serializing its URL from the stream. + /// + protected Factory(SerializationInfo info, StreamingContext context) : base(info, context) + { + // do nothing. + } + + //====================================================================== + // IOpcFactory + + /// + /// Creates a new instance of the server. + /// + public override IOpcServer CreateInstance(OpcUrl url, OpcConnectData connectData) + { + var comServer = Factory.Connect(url, connectData); + + if (comServer == null) + { + return null; + } + + Server server = null; + Type interfaceType = null; + + try + { + if (string.IsNullOrEmpty(url.Scheme)) + { + throw new NotSupportedException(string.Format("The URL scheme '{0}' is not supported.", url.Scheme)); + } + + // DA + else if (url.Scheme == OpcUrlScheme.DA) + { + // Verify that it is a DA server. + if (!typeof(OpcRcw.Da.IOPCServer).IsInstanceOfType(comServer)) + { + interfaceType = typeof(OpcRcw.Da.IOPCServer); + throw new NotSupportedException(); + } + + // DA 3.00 + if (!ForceDa20Usage && typeof(OpcRcw.Da.IOPCBrowse).IsInstanceOfType(comServer) && typeof(OpcRcw.Da.IOPCItemIO).IsInstanceOfType(comServer)) + { + server = new Technosoftware.DaAeHdaClient.Com.Da.Server(url, comServer); + } + + // DA 2.XX + else if (typeof(OpcRcw.Da.IOPCItemProperties).IsInstanceOfType(comServer)) + { + server = new Technosoftware.DaAeHdaClient.Com.Da20.Server(url, comServer); + } + + else + { + interfaceType = typeof(OpcRcw.Da.IOPCItemProperties); + throw new NotSupportedException(); + } + } + + // AE + else if (url.Scheme == OpcUrlScheme.AE) + { + // Verify that it is a AE server. + if (!typeof(OpcRcw.Ae.IOPCEventServer).IsInstanceOfType(comServer)) + { + interfaceType = typeof(OpcRcw.Ae.IOPCEventServer); + throw new NotSupportedException(); + } + + server = new Technosoftware.DaAeHdaClient.Com.Ae.Server(url, comServer); + } + + // HDA + else if (url.Scheme == OpcUrlScheme.HDA) + { + // Verify that it is a HDA server. + if (!typeof(OpcRcw.Hda.IOPCHDA_Server).IsInstanceOfType(comServer)) + { + interfaceType = typeof(OpcRcw.Hda.IOPCHDA_Server); + throw new NotSupportedException(); + } + + server = new Technosoftware.DaAeHdaClient.Com.Hda.Server(url, comServer); + } + + // All other specifications not supported yet. + else + { + throw new NotSupportedException(string.Format("The URL scheme '{0}' is not supported.", url.Scheme)); + } + } + catch (NotSupportedException) + { + Utilities.Interop.ReleaseServer(server); + server = null; + + if (interfaceType != null) + { + var message = new StringBuilder(); + + message.AppendFormat("The COM server does not support the interface "); + message.AppendFormat("'{0}'.", interfaceType.FullName); + message.Append("\r\n\r\nThis problem could be caused by:\r\n"); + message.Append("- incorrectly installed proxy/stubs.\r\n"); + message.Append("- problems with the DCOM security settings.\r\n"); + message.Append("- a personal firewall (sometimes activated by default).\r\n"); + + throw new NotSupportedException(message.ToString()); + } + + throw; + } + catch (Exception) + { + Utilities.Interop.ReleaseServer(server); + throw; + } + + // initialize the wrapper object. + if (server != null) + { + server.Initialize(url, connectData); + } + + SupportedSpecifications = new List(); + if (server is Com.Da20.Server) + { + SupportedSpecifications.Add(OpcSpecification.OPC_DA_20); + } + else if (server is Com.Da.Server) + { + SupportedSpecifications.Add(OpcSpecification.OPC_DA_30); + SupportedSpecifications.Add(OpcSpecification.OPC_DA_20); + } + else if (server is Com.Ae.Server) + { + SupportedSpecifications.Add(OpcSpecification.OPC_AE_10); + } + else if (server is Com.Hda.Server) + { + SupportedSpecifications.Add(OpcSpecification.OPC_HDA_10); + } + + return server; + } + + /// + /// Connects to the specified COM server. + /// + public static object Connect(OpcUrl url, OpcConnectData connectData) + { + // parse path to find prog id and clsid. + var progID = url.Path; + string clsid = null; + + var index = url.Path.IndexOf('/'); + + if (index >= 0) + { + progID = url.Path.Substring(0, index); + clsid = url.Path.Substring(index + 1); + } + + // look up prog id if clsid not specified in the url. + Guid guid; + + if (clsid == null) + { + // use OpcEnum to lookup the prog id. + guid = new ServerEnumerator().CLSIDFromProgID(progID, url.HostName, connectData); + + // check if prog id is actually a clsid string. + if (guid == Guid.Empty) + { + try + { + guid = new Guid(progID); + } + catch + { + throw new OpcResultException(new OpcResult((int)OpcResult.CO_E_CLASSSTRING.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Could not connect to server {0}", progID)); + } + } + } + + // convert clsid string to a guid. + else + { + try + { + guid = new Guid(clsid); + } + catch + { + throw new OpcResultException(new OpcResult((int)OpcResult.CO_E_CLASSSTRING.Code, OpcResult.FuncCallType.SysFuncCall, null), string.Format("Could not connect to server {0}", progID)); + } + } + + // get the credentials. + var credentials = (connectData != null) ? connectData.UserIdentity : null; + + // instantiate the server using CoCreateInstanceEx. + if (connectData == null || connectData.LicenseKey == null) + { + try + { + return Utilities.Interop.CreateInstance(guid, url.HostName, credentials); + } + catch (Exception e) + { + throw new OpcResultException(OpcResult.CO_E_CLASSSTRING, e.Message, e); + } + } + + // instantiate the server using IClassFactory2. + else + { + try + { + return null; + //return Technosoftware.DaAeHdaClient.Utilities.Interop.CreateInstanceWithLicenseKey(guid, url.HostName, credentials, connectData.LicenseKey); + } + catch (Exception e) + { + throw new OpcResultException(OpcResult.CO_E_CLASSSTRING, e.Message, e); + } + } + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Hda/Browser.cs b/Technosoftware/DaAeHdaClient.Com/Hda/Browser.cs new file mode 100644 index 0000000..fa7fbfe --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Hda/Browser.cs @@ -0,0 +1,441 @@ +#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; + +using Technosoftware.DaAeHdaClient.Hda; +using Technosoftware.OpcRcw.Hda; +using Technosoftware.OpcRcw.Comn; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Hda +{ + /// + /// An in-process wrapper an OPC HDA browser object. + /// + internal class Browser : ITsCHdaBrowser + { + //====================================================================== + // Construction + + /// + /// Initializes the object with the specifed COM server. + /// + internal Browser(Server server, IOPCHDA_Browser browser, TsCHdaBrowseFilter[] filters, OpcResult[] results) + { + if (browser == null) throw new ArgumentNullException(nameof(browser)); + + // save the server object that created the browser. + m_server = server; + + // save the COM server (released in Dispose()). + m_browser = browser; + + // save only the filters that were accepted. + if (filters != null) + { + var validFilters = new ArrayList(); + + for (var ii = 0; ii < filters.Length; ii++) + { + if (results[ii].Succeeded()) + { + validFilters.Add(filters[ii]); + } + } + + m_filters = new TsCHdaBrowseFilterCollection(validFilters); + } + } + + #region IDisposable Members + /// + /// This must be called explicitly by clients to ensure the COM server is released. + /// + public virtual void Dispose() + { + lock (this) + { + m_server = null; + Utilities.Interop.ReleaseServer(m_browser); + m_browser = null; + } + } + #endregion + + //====================================================================== + // Filters + + /// + /// Returns the set of attribute filters used by the browser. + /// + public TsCHdaBrowseFilterCollection Filters + { + get + { + lock (this) + { + return (TsCHdaBrowseFilterCollection)m_filters.Clone(); + } + } + } + + //====================================================================== + // Browse + + /// + /// Browses the server's address space at the specified branch. + /// + /// The item id of the branch to search. + /// The set of elements that meet the filter criteria. + public TsCHdaBrowseElement[] Browse(OpcItem itemID) + { + IOpcBrowsePosition position; + var elements = Browse(itemID, 0, out position); + + if (position != null) + { + position.Dispose(); + } + + return elements; + } + + /// + /// Begins a browsing the server's address space at the specified branch. + /// + /// The item id of the branch to search. + /// The maximum number of elements to return. + /// The position object used to continue a browse operation. + /// The set of elements that meet the filter criteria. + public TsCHdaBrowseElement[] Browse(OpcItem itemID, int maxElements, out IOpcBrowsePosition position) + { + position = null; + + // interpret invalid values as 'no limit'. + if (maxElements <= 0) + { + maxElements = int.MaxValue; + } + + lock (this) + { + var branchPath = (itemID != null && itemID.ItemName != null)?itemID.ItemName:""; + + // move to the correct position in the server's address space. + try + { + m_browser.ChangeBrowsePosition(OPCHDA_BROWSEDIRECTION.OPCHDA_BROWSE_DIRECT, branchPath); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Browser.ChangeBrowsePosition", e); + } + + // browse for branches + var enumerator = GetEnumerator(true); + + var elements = FetchElements(enumerator, maxElements, true); + + // check if max element count reached. + if (elements.Count >= maxElements) + { + position = new BrowsePosition(branchPath, enumerator, false); + return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement)); + } + + // release enumerator. + enumerator.Dispose(); + + // browse for items + enumerator = GetEnumerator(false); + + var items = FetchElements(enumerator, maxElements-elements.Count, false); + + if (items != null) + { + elements.AddRange(items); + } + + // check if max element count reached. + if (elements.Count >= maxElements) + { + position = new BrowsePosition(branchPath, enumerator, true); + return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement)); + } + + // release enumerator. + enumerator.Dispose(); + + return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement)); + } + } + + //====================================================================== + // BrowseNext + + /// + /// Continues browsing the server's address space at the specified position. + /// + /// The maximum number of elements to return. + /// The position object used to continue a browse operation. + /// The set of elements that meet the filter criteria. + public TsCHdaBrowseElement[] BrowseNext(int maxElements, ref IOpcBrowsePosition position) + { + // check arguments. + if (position == null || position.GetType() != typeof(BrowsePosition)) + { + throw new ArgumentException("Not a valid browse position object.", nameof(position)); + } + + // interpret invalid values as 'no limit'. + if (maxElements <= 0) + { + maxElements = int.MaxValue; + } + + lock (this) + { + var pos = (BrowsePosition)position; + + var elements = new ArrayList(); + + if (!pos.FetchingItems) + { + elements = FetchElements(pos.Enumerator, maxElements, true); + + // check if max element count reached. + if (elements.Count >= maxElements) + { + return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement)); + } + + // release enumerator. + pos.Enumerator.Dispose(); + + pos.Enumerator = null; + pos.FetchingItems = true; + + // move to the correct position in the server's address space. + try + { + m_browser.ChangeBrowsePosition(OPCHDA_BROWSEDIRECTION.OPCHDA_BROWSE_DIRECT, pos.BranchPath); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Browser.ChangeBrowsePosition", e); + } + + // create enumerator for items. + pos.Enumerator = GetEnumerator(false); + } + + // fetch next set of items. + var items = FetchElements(pos.Enumerator, maxElements-elements.Count, false); + + if (items != null) + { + elements.AddRange(items); + } + + // check if max element count reached. + if (elements.Count >= maxElements) + { + return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement)); + } + + // release position object. + position.Dispose(); + position = null; + + // return elements. + return (TsCHdaBrowseElement[])elements.ToArray(typeof(TsCHdaBrowseElement)); + } + } + + #region Private Methods + /// + /// Creates an enumerator for the elements contained with the current branch. + /// + private EnumString GetEnumerator(bool isBranch) + { + try + { + var browseType = (isBranch)?OPCHDA_BROWSETYPE.OPCHDA_BRANCH:OPCHDA_BROWSETYPE.OPCHDA_LEAF; + + IEnumString pEnumerator = null; + m_browser.GetEnum(browseType, out pEnumerator); + + return new EnumString(pEnumerator); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Browser.GetEnum", e); + } + } + + /// + /// Fetches the element names and item ids for each element. + /// + private ArrayList FetchElements(EnumString enumerator, int maxElements, bool isBranch) + { + var elements = new ArrayList(); + + while (elements.Count < maxElements) + { + // fetch next batch of element names. + var count = BLOCK_SIZE; + + if (elements.Count + count > maxElements) + { + count = maxElements - elements.Count; + } + var names = enumerator.Next(count); + + // check if no more elements found. + if (names == null || names.Length == 0) + { + break; + } + + // create new element objects. + foreach (var name in names) + { + var element = new TsCHdaBrowseElement(); + + element.Name = name; + // lookup item id for element. + try + { + string itemID = null; + m_browser.GetItemID(name, out itemID); + + element.ItemName = itemID; + element.ItemPath = null; + element.HasChildren = isBranch; + } + catch + { + // ignore errors. + } + + elements.Add(element); + } + } + + // validate items - this is necessary to set the IsItem flag correctly. + var results = m_server.ValidateItems((OpcItem[])elements.ToArray(typeof(OpcItem))); + + if (results != null) + { + for (var ii = 0; ii < results.Length; ii++) + { + if (results[ii].Result.Succeeded()) + { + ((TsCHdaBrowseElement)elements[ii]).IsItem = true; + } + } + } + + // return results. + return elements; + } + #endregion + + #region Private Members + private Server m_server = null; + private IOPCHDA_Browser m_browser = null; + private TsCHdaBrowseFilterCollection m_filters = new TsCHdaBrowseFilterCollection(); + private const int BLOCK_SIZE = 10; + #endregion + } + + /// + /// Stores the state of a browse operation that was halted. + /// + internal class BrowsePosition : TsCHdaBrowsePosition + { + /// + /// Initializes a the object with the browse operation state information. + /// + /// The item id of branch used in the browse operation. + /// The enumerator used for the browse operation. + /// Whether the enumerator is return branches or items. + internal BrowsePosition(string branchPath, EnumString enumerator, bool fetchingItems) + { + m_branchPath = branchPath; + m_enumerator = enumerator; + m_fetchingItems = fetchingItems; + } + + /// + /// The item id of the branch being browsed. + /// + internal string BranchPath + { + get => m_branchPath; + set => m_branchPath = value; + } + + /// + /// The enumerator that was in use when the browse halted. + /// + internal EnumString Enumerator + { + get => m_enumerator; + set => m_enumerator = value; + } + + /// + /// Whether the browse halted while fetching items. + /// + internal bool FetchingItems + { + get => m_fetchingItems; + set => m_fetchingItems = value; + } + + #region IDisposable Members + /// + /// Releases any unmanaged resources held by the object. + /// + public override void Dispose() + { + if (m_enumerator != null) + { + m_enumerator.Dispose(); + m_enumerator = null; + } + + base.Dispose(); + } + #endregion + + #region Private Members + private string m_branchPath = null; + private EnumString m_enumerator = null; + private bool m_fetchingItems = false; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Hda/DataCallback.cs b/Technosoftware/DaAeHdaClient.Com/Hda/DataCallback.cs new file mode 100644 index 0000000..3bdc0d1 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Hda/DataCallback.cs @@ -0,0 +1,591 @@ +#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; + +using Technosoftware.DaAeHdaClient.Hda; +using Technosoftware.OpcRcw.Hda; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Hda +{ + /// + /// A class that implements the HDA data callback interface. + /// + internal class DataCallback : IOPCHDA_DataCallback + { + /// + /// Initializes the object with the containing subscription object. + /// + public DataCallback() {} + + /// + /// Fired when an exception occurs during callback processing. + /// + public event TsCHdaCallbackExceptionEventHandler CallbackExceptionEvent + { + add {lock (this) { _callbackExceptionEvent += value; }} + remove {lock (this) { _callbackExceptionEvent -= value; }} + } + + /// + /// Creates a new request object. + /// + public Request CreateRequest(object requestHandle, Delegate callback) + { + lock (this) + { + // create a new request. + var request = new Request(requestHandle, callback, ++m_nextID); + + // no items yet - callback may return before async call returns. + m_requests[request.RequestID] = request; + + // return requests. + return request; + } + } + + /// + /// Cancels an existing request. + /// + public bool CancelRequest(Request request, TsCHdaCancelCompleteEventHandler callback) + { + lock (this) + { + // check if it is a valid request. + if (!m_requests.Contains(request.RequestID)) + { + return false; + } + + // request will be removed when the cancel complete callback arrives. + if (callback != null) + { + request.CancelCompleteEvent += callback; + } + + // no confirmation required - remove request immediately. + else + { + m_requests.Remove(request.RequestID); + } + + // request will be cancelled. + return true; + } + } + + #region IOPCHDA_DataCallback Members + /// + /// Called when new data arrives for a subscription. + /// + public void OnDataChange( + int dwTransactionID, + int hrStatus, + int dwNumItems, + OPCHDA_ITEM[] pItemValues, + int[] phrErrors) + { + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // unmarshal results. + var results = new TsCHdaItemValueCollection[pItemValues.Length]; + + for (var ii = 0; ii < pItemValues.Length; ii++) + { + results[ii] = Interop.GetItemValueCollection(pItemValues[ii], false); + + results[ii].ServerHandle = results[ii].ClientHandle; + results[ii].ClientHandle = null; + results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]); + } + + // invoke callback - remove request if unexpected error occured. + if (request.InvokeCallback(results)) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when an asynchronous read request completes. + /// + public void OnReadComplete( + int dwTransactionID, + int hrStatus, + int dwNumItems, + OPCHDA_ITEM[] pItemValues, + int[] phrErrors) + { + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // unmarshal results. + var results = new TsCHdaItemValueCollection[pItemValues.Length]; + + for (var ii = 0; ii < pItemValues.Length; ii++) + { + results[ii] = Interop.GetItemValueCollection(pItemValues[ii], false); + + results[ii].ServerHandle = pItemValues[ii].hClient; + results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]); + } + + // invoke callback - remove request if all results arrived. + if (request.InvokeCallback(results)) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when an asynchronous read modified request completes. + /// + public void OnReadModifiedComplete( + int dwTransactionID, + int hrStatus, + int dwNumItems, + OPCHDA_MODIFIEDITEM[] pItemValues, + int[] phrErrors) + { + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // unmarshal results. + var results = new TsCHdaModifiedValueCollection[pItemValues.Length]; + + for (var ii = 0; ii < pItemValues.Length; ii++) + { + results[ii] = Interop.GetModifiedValueCollection(pItemValues[ii], false); + + results[ii].ServerHandle = pItemValues[ii].hClient; + results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]); + } + + // invoke callback - remove request if all results arrived. + if (request.InvokeCallback(results)) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when an asynchronous read attributes request completes. + /// + public void OnReadAttributeComplete( + int dwTransactionID, + int hrStatus, + int hClient, + int dwNumItems, + OPCHDA_ATTRIBUTE[] pAttributeValues, + int[] phrErrors) + { + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // create item object to collect results. + var item = new TsCHdaItemAttributeCollection(); + item.ServerHandle = hClient; + + // unmarshal results. + var results = new TsCHdaAttributeValueCollection[pAttributeValues.Length]; + + for (var ii = 0; ii < pAttributeValues.Length; ii++) + { + results[ii] = Interop.GetAttributeValueCollection(pAttributeValues[ii], false); + + results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]); + + item.Add(results[ii]); + } + + // invoke callback - remove request if all results arrived. + if (request.InvokeCallback(item)) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when an asynchronous read annotations request completes. + /// + public void OnReadAnnotations( + int dwTransactionID, + int hrStatus, + int dwNumItems, + OPCHDA_ANNOTATION[] pAnnotationValues, + int[] phrErrors) + { + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // unmarshal results. + var results = new TsCHdaAnnotationValueCollection[pAnnotationValues.Length]; + + for (var ii = 0; ii < pAnnotationValues.Length; ii++) + { + results[ii] = Interop.GetAnnotationValueCollection(pAnnotationValues[ii], false); + + results[ii].ServerHandle = pAnnotationValues[ii].hClient; + results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]); + } + + // invoke callback - remove request if all results arrived. + if (request.InvokeCallback(results)) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when an asynchronous insert annotations request completes. + /// + public void OnInsertAnnotations( + int dwTransactionID, + int hrStatus, + int dwCount, + int[] phClients, + int[] phrErrors) + { + + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // unmarshal results. + var results = new ArrayList(); + + if (dwCount > 0) + { + // subscription results in collections for the same item id. + var currentHandle = phClients[0]; + + var itemResults = new TsCHdaResultCollection(); + + for (var ii = 0; ii < dwCount; ii++) + { + // create a new collection for the next item's results. + if (phClients[ii] != currentHandle) + { + itemResults.ServerHandle = currentHandle; + results.Add(itemResults); + + currentHandle = phClients[ii]; + itemResults = new TsCHdaResultCollection(); + } + + var result = new TsCHdaResult(Utilities.Interop.GetResultId(phrErrors[ii])); + itemResults.Add(result); + } + + // add the last set of item results. + itemResults.ServerHandle = currentHandle; + results.Add(itemResults); + } + + // invoke callback - remove request if all results arrived. + if (request.InvokeCallback((TsCHdaResultCollection[])results.ToArray(typeof(TsCHdaResultCollection)))) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when a batch of data from playback request arrives. + /// + public void OnPlayback( + int dwTransactionID, + int hrStatus, + int dwNumItems, + IntPtr ppItemValues, + int[] phrErrors) + { + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // unmarshal results. + var results = new TsCHdaItemValueCollection[dwNumItems]; + + // the data is transfered as a array of pointers to items instead of simply + // as an array of items. This is due to a mistake in the HDA IDL. + var pItems = Utilities.Interop.GetInt32s(ref ppItemValues, dwNumItems, false); + + for (var ii = 0; ii < dwNumItems; ii++) + { + // get pointer to item. + var pItem = (IntPtr)pItems[ii]; + + // unmarshal item as an array of length 1. + var item = Interop.GetItemValueCollections(ref pItem, 1, false); + + if (item != null && item.Length == 1) + { + results[ii] = item[0]; + results[ii].ServerHandle = results[ii].ClientHandle; + results[ii].ClientHandle = null; + results[ii].Result = Utilities.Interop.GetResultId(phrErrors[ii]); + } + } + + // invoke callback - remove request if unexpected error occured. + if (request.InvokeCallback(results)) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when an asynchronous update request completes. + /// + public void OnUpdateComplete( + int dwTransactionID, + int hrStatus, + int dwCount, + int[] phClients, + int[] phrErrors) + { + try + { + lock (this) + { + // lookup request transaction. + var request = (Request)m_requests[dwTransactionID]; + + if (request == null) + { + return; + } + + // unmarshal results. + var results = new ArrayList(); + + if (dwCount > 0) + { + // subscription results in collections for the same item id. + var currentHandle = phClients[0]; + + var itemResults = new TsCHdaResultCollection(); + + for (var ii = 0; ii < dwCount; ii++) + { + // create a new collection for the next item's results. + if (phClients[ii] != currentHandle) + { + itemResults.ServerHandle = currentHandle; + results.Add(itemResults); + + currentHandle = phClients[ii]; + itemResults = new TsCHdaResultCollection(); + } + + var result = new TsCHdaResult(Utilities.Interop.GetResultId(phrErrors[ii])); + itemResults.Add(result); + } + + // add the last set of item results. + itemResults.ServerHandle = currentHandle; + results.Add(itemResults); + } + + // invoke callback - remove request if all results arrived. + if (request.InvokeCallback((TsCHdaResultCollection[])results.ToArray(typeof(TsCHdaResultCollection)))) + { + m_requests.Remove(request.RequestID); + } + } + } + catch (Exception exception) + { + HandleException(dwTransactionID, exception); + } + } + + /// + /// Called when an asynchronous request was cancelled successfully. + /// + public void OnCancelComplete(int dwCancelID) + { + try + { + lock (this) + { + // lookup request. + var request = (Request)m_requests[dwCancelID]; + + if (request == null) + { + return; + } + + // send the cancel complete notification. + request.OnCancelComplete(); + + // remove the request. + m_requests.Remove(request.RequestID); + } + } + catch (Exception exception) + { + HandleException(dwCancelID, exception); + } + } + #endregion + + #region Private Methods + /// + /// Fires an event indicating an exception occurred during callback processing. + /// + void HandleException(int requestID, Exception exception) + { + lock (this) + { + // lookup request. + var request = (Request)m_requests[requestID]; + + if (request != null) + { + // send notification. + if (_callbackExceptionEvent != null) + { + _callbackExceptionEvent(request, exception); + } + } + } + } + #endregion + + #region Private Members + private int m_nextID; + private Hashtable m_requests = new Hashtable(); + private TsCHdaCallbackExceptionEventHandler _callbackExceptionEvent; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Hda/Interop.cs b/Technosoftware/DaAeHdaClient.Com/Hda/Interop.cs new file mode 100644 index 0000000..6fc417b --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Hda/Interop.cs @@ -0,0 +1,441 @@ +#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.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Da; +using Technosoftware.DaAeHdaClient.Hda; +#endregion + +#pragma warning disable 0618 + +namespace Technosoftware.DaAeHdaClient.Com.Hda +{ + /// + /// Contains state information for a single asynchronous Technosoftware.DaAeHdaClient.Com.Hda.Interop. + /// + internal class Interop + { + /// + /// Converts a standard FILETIME to an OpcRcw.Da.FILETIME structure. + /// + internal static OpcRcw.Hda.OPCHDA_FILETIME Convert(FILETIME input) + { + var output = new OpcRcw.Hda.OPCHDA_FILETIME(); + output.dwLowDateTime = input.dwLowDateTime; + output.dwHighDateTime = input.dwHighDateTime; + return output; + } + + /// + /// Converts an OpcRcw.Da.FILETIME to a standard FILETIME structure. + /// + internal static FILETIME Convert(OpcRcw.Hda.OPCHDA_FILETIME input) + { + var output = new FILETIME(); + output.dwLowDateTime = input.dwLowDateTime; + output.dwHighDateTime = input.dwHighDateTime; + return output; + } + + /// + /// Converts a decimal value to a OpcRcw.Hda.OPCHDA_TIME structure. + /// + internal static OpcRcw.Hda.OPCHDA_FILETIME GetFILETIME(decimal input) + { + var output = new OpcRcw.Hda.OPCHDA_FILETIME(); + + output.dwHighDateTime = (int)((((ulong)(input*10000000)) & 0xFFFFFFFF00000000)>>32); + output.dwLowDateTime = (int)((((ulong)(input*10000000)) & 0x00000000FFFFFFFF)); + + return output; + } + + /// + /// Returns an array of FILETIMEs. + /// + internal static OpcRcw.Hda.OPCHDA_FILETIME[] GetFILETIMEs(DateTime[] input) + { + OpcRcw.Hda.OPCHDA_FILETIME[] output = null; + + if (input != null) + { + output = new OpcRcw.Hda.OPCHDA_FILETIME[input.Length]; + + for (var ii = 0; ii < input.Length; ii++) + { + output[ii] = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input[ii])); + } + } + + return output; + } + + /// + /// Converts a Technosoftware.DaAeHdaClient.Time object to a Technosoftware.DaAeHdaClient.Com.Hda.OPCHDA_TIME structure. + /// + internal static OpcRcw.Hda.OPCHDA_TIME GetTime(TsCHdaTime input) + { + var output = new OpcRcw.Hda.OPCHDA_TIME(); + + if (input != null) + { + output.ftTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(input.AbsoluteTime)); + output.szTime = (input.IsRelative)?input.ToString():""; + output.bString = (input.IsRelative)?1:0; + } + + // create a null value for a time structure. + else + { + output.ftTime = Convert(Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(DateTime.MinValue)); + output.szTime = ""; + output.bString = 1; + } + + return output; + } + + /// + /// Unmarshals and deallocates an array of OPCHDA_ITEM structures. + /// + internal static TsCHdaItemValueCollection[] GetItemValueCollections(ref IntPtr pInput, int count, bool deallocate) + { + TsCHdaItemValueCollection[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCHdaItemValueCollection[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + output[ii] = GetItemValueCollection(pos, deallocate); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_ITEM))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_ITEM structure. + /// + internal static TsCHdaItemValueCollection GetItemValueCollection(IntPtr pInput, bool deallocate) + { + TsCHdaItemValueCollection output = null; + + if (pInput != IntPtr.Zero) + { + var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ITEM)); + + output = GetItemValueCollection((OpcRcw.Hda.OPCHDA_ITEM)item, deallocate); + + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ITEM)); + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_ITEM structure. + /// + internal static TsCHdaItemValueCollection GetItemValueCollection(OpcRcw.Hda.OPCHDA_ITEM input, bool deallocate) + { + var output = new TsCHdaItemValueCollection(); + + output.ClientHandle = input.hClient; + output.Aggregate = input.haAggregate; + + var values = Com.Interop.GetVARIANTs(ref input.pvDataValues, input.dwCount, deallocate); + var timestamps = Utilities.Interop.GetDateTimes(ref input.pftTimeStamps, input.dwCount, deallocate); + var qualities = Utilities.Interop.GetInt32s(ref input.pdwQualities, input.dwCount, deallocate); + + for (var ii = 0; ii < input.dwCount; ii++) + { + var value = new TsCHdaItemValue(); + + value.Value = values[ii]; + value.Timestamp = timestamps[ii]; + value.Quality = new TsCDaQuality((short)(qualities[ii] & 0x0000FFFF)); + value.HistorianQuality = (TsCHdaQuality)((int)(qualities[ii] & 0xFFFF0000)); + + output.Add(value); + } + + return output; + } + + /// + /// Unmarshals and deallocates an array of OPCHDA_MODIFIEDITEM structures. + /// + internal static TsCHdaModifiedValueCollection[] GetModifiedValueCollections(ref IntPtr pInput, int count, bool deallocate) + { + TsCHdaModifiedValueCollection[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCHdaModifiedValueCollection[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + output[ii] = GetModifiedValueCollection(pos, deallocate); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_MODIFIEDITEM))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_MODIFIEDITEM structure. + /// + internal static TsCHdaModifiedValueCollection GetModifiedValueCollection(IntPtr pInput, bool deallocate) + { + TsCHdaModifiedValueCollection output = null; + + if (pInput != IntPtr.Zero) + { + var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_MODIFIEDITEM)); + + output = GetModifiedValueCollection((OpcRcw.Hda.OPCHDA_MODIFIEDITEM)item, deallocate); + + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_MODIFIEDITEM)); + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_MODIFIEDITEM structure. + /// + internal static TsCHdaModifiedValueCollection GetModifiedValueCollection(OpcRcw.Hda.OPCHDA_MODIFIEDITEM input, bool deallocate) + { + var output = new TsCHdaModifiedValueCollection(); + + output.ClientHandle = input.hClient; + + var values = Com.Interop.GetVARIANTs(ref input.pvDataValues, input.dwCount, deallocate); + var timestamps = Utilities.Interop.GetDateTimes(ref input.pftTimeStamps, input.dwCount, deallocate); + var qualities = Utilities.Interop.GetInt32s(ref input.pdwQualities, input.dwCount, deallocate); + var modificationTimes = Utilities.Interop.GetDateTimes(ref input.pftModificationTime, input.dwCount, deallocate); + var editTypes = Utilities.Interop.GetInt32s(ref input.pEditType, input.dwCount, deallocate); + var users = Utilities.Interop.GetUnicodeStrings(ref input.szUser, input.dwCount, deallocate); + + for (var ii = 0; ii < input.dwCount; ii++) + { + var value = new TsCHdaModifiedValue(); + + value.Value = values[ii]; + value.Timestamp = timestamps[ii]; + value.Quality = new TsCDaQuality((short)(qualities[ii] & 0x0000FFFF)); + value.HistorianQuality = (TsCHdaQuality)((int)(qualities[ii] & 0xFFFF0000)); + value.ModificationTime = modificationTimes[ii]; + value.EditType = (TsCHdaEditType)editTypes[ii]; + value.User = users[ii]; + + output.Add(value); + } + + return output; + } + + /// + /// Unmarshals and deallocates an array of OPCHDA_ATTRIBUTE structures. + /// + internal static TsCHdaAttributeValueCollection[] GetAttributeValueCollections(ref IntPtr pInput, int count, bool deallocate) + { + TsCHdaAttributeValueCollection[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCHdaAttributeValueCollection[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + output[ii] = GetAttributeValueCollection(pos, deallocate); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_ATTRIBUTE))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_ATTRIBUTE structure. + /// + internal static TsCHdaAttributeValueCollection GetAttributeValueCollection(IntPtr pInput, bool deallocate) + { + TsCHdaAttributeValueCollection output = null; + + if (pInput != IntPtr.Zero) + { + var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ATTRIBUTE)); + + output = GetAttributeValueCollection((OpcRcw.Hda.OPCHDA_ATTRIBUTE)item, deallocate); + + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ATTRIBUTE)); + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_ATTRIBUTE structure. + /// + internal static TsCHdaAttributeValueCollection GetAttributeValueCollection(OpcRcw.Hda.OPCHDA_ATTRIBUTE input, bool deallocate) + { + var output = new TsCHdaAttributeValueCollection(); + + output.AttributeID = input.dwAttributeID; + + var values = Com.Interop.GetVARIANTs(ref input.vAttributeValues, input.dwNumValues, deallocate); + var timestamps = Utilities.Interop.GetDateTimes(ref input.ftTimeStamps, input.dwNumValues, deallocate); + + for (var ii = 0; ii < input.dwNumValues; ii++) + { + var value = new TsCHdaAttributeValue(); + + value.Value = values[ii]; + value.Timestamp = timestamps[ii]; + + output.Add(value); + } + + return output; + } + + /// + /// Unmarshals and deallocates an array of OPCHDA_ANNOTATION structures. + /// + internal static TsCHdaAnnotationValueCollection[] GetAnnotationValueCollections(ref IntPtr pInput, int count, bool deallocate) + { + TsCHdaAnnotationValueCollection[] output = null; + + if (pInput != IntPtr.Zero && count > 0) + { + output = new TsCHdaAnnotationValueCollection[count]; + + var pos = pInput; + + for (var ii = 0; ii < count; ii++) + { + output[ii] = GetAnnotationValueCollection(pos, deallocate); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(OpcRcw.Hda.OPCHDA_ANNOTATION))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_ANNOTATION structure. + /// + internal static TsCHdaAnnotationValueCollection GetAnnotationValueCollection(IntPtr pInput, bool deallocate) + { + TsCHdaAnnotationValueCollection output = null; + + if (pInput != IntPtr.Zero) + { + var item = Marshal.PtrToStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ANNOTATION)); + + output = GetAnnotationValueCollection((OpcRcw.Hda.OPCHDA_ANNOTATION)item, deallocate); + + if (deallocate) + { + Marshal.DestroyStructure(pInput, typeof(OpcRcw.Hda.OPCHDA_ANNOTATION)); + } + } + + return output; + } + + /// + /// Unmarshals and deallocates an OPCHDA_ANNOTATION structure. + /// + internal static TsCHdaAnnotationValueCollection GetAnnotationValueCollection(OpcRcw.Hda.OPCHDA_ANNOTATION input, bool deallocate) + { + var output = new TsCHdaAnnotationValueCollection(); + + output.ClientHandle = input.hClient; + + var timestamps = Utilities.Interop.GetDateTimes(ref input.ftTimeStamps, input.dwNumValues, deallocate); + var annotations = Utilities.Interop.GetUnicodeStrings(ref input.szAnnotation, input.dwNumValues, deallocate); + var creationTimes = Utilities.Interop.GetDateTimes(ref input.ftAnnotationTime, input.dwNumValues, deallocate); + var users = Utilities.Interop.GetUnicodeStrings(ref input.szUser, input.dwNumValues, deallocate); + + for (var ii = 0; ii < input.dwNumValues; ii++) + { + var value = new TsCHdaAnnotationValue(); + + value.Timestamp = timestamps[ii]; + value.Annotation = annotations[ii]; + value.CreationTime = creationTimes[ii]; + value.User = users[ii]; + + output.Add(value); + } + + return output; + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Hda/Request.cs b/Technosoftware/DaAeHdaClient.Com/Hda/Request.cs new file mode 100644 index 0000000..11d16d1 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Hda/Request.cs @@ -0,0 +1,404 @@ +#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; + +using Technosoftware.DaAeHdaClient.Hda; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Hda +{ + /// + /// An object that mainatains the state of asynchronous requests. + /// + internal class Request : IOpcRequest, ITsCHdaActualTime + { + /// + /// The unique id assigned to the request when it was created. + /// + public int RequestID => m_requestID; + + /// + /// The unqiue id assigned by the server when it was created. + /// + public int CancelID => m_cancelID; + + /// + /// Fired when the server acknowledges that a request was cancelled. + /// + public event TsCHdaCancelCompleteEventHandler CancelCompleteEvent + { + add { lock (this) { m_cancelCompleteEvent += value; } } + remove { lock (this) { m_cancelCompleteEvent -= value; } } + } + + /// + /// Initializes the object with all required information. + /// + public Request(object requestHandle, Delegate callback, int requestID) + { + m_requestHandle = requestHandle; + m_callback = callback; + m_requestID = requestID; + } + + /// + /// Updates the request with the initial results. + /// + public bool Update(int cancelID, OpcItem[] results) + { + lock (this) + { + // save the server assigned id. + m_cancelID = cancelID; + + // create a table of items indexed by the handle returned by the server in a callback. + m_items = new Hashtable(); + + foreach (var result in results) + { + if (!typeof(IOpcResult).IsInstanceOfType(result) || ((IOpcResult)result).Result.Succeeded()) + { + m_items[result.ServerHandle] = new OpcItem(result); + } + } + + // nothing more to do - no good items. + if (m_items.Count == 0) + { + return true; + } + + // invoke callbacks for results that have already arrived. + var complete = false; + + if (m_results != null) + { + foreach (var result in m_results) + { + complete = InvokeCallback(result); + } + } + + // all done. + return complete; + } + } + + /// + /// Invokes the callback for the request. + /// + public bool InvokeCallback(object results) + { + lock (this) + { + // save the results if the initial call to the server has not completed yet. + if (m_items == null) + { + // create cache for results. + if (m_results == null) + { + m_results = new ArrayList(); + } + + m_results.Add(results); + + // request not initialized completely + return false; + } + + // invoke on data update callback. + if (typeof(TsCHdaDataUpdateEventHandler).IsInstanceOfType(m_callback)) + { + return InvokeCallback((TsCHdaDataUpdateEventHandler)m_callback, results); + } + + // invoke read completed callback. + if (typeof(TsCHdaReadValuesCompleteEventHandler).IsInstanceOfType(m_callback)) + { + return InvokeCallback((TsCHdaReadValuesCompleteEventHandler)m_callback, results); + } + + // invoke read attributes completed callback. + if (typeof(TsCHdaReadAttributesCompleteEventHandler).IsInstanceOfType(m_callback)) + { + return InvokeCallback((TsCHdaReadAttributesCompleteEventHandler)m_callback, results); + } + + // invoke read annotations completed callback. + if (typeof(TsCHdaReadAnnotationsCompleteEventHandler).IsInstanceOfType(m_callback)) + { + return InvokeCallback((TsCHdaReadAnnotationsCompleteEventHandler)m_callback, results); + } + + // invoke update completed callback. + if (typeof(TsCHdaUpdateCompleteEventHandler).IsInstanceOfType(m_callback)) + { + return InvokeCallback((TsCHdaUpdateCompleteEventHandler)m_callback, results); + } + + // callback not supported. + return true; + } + } + + /// + /// Called when the server acknowledges that a request was cancelled. + /// + public void OnCancelComplete() + { + lock (this) + { + if (m_cancelCompleteEvent != null) + { + m_cancelCompleteEvent(this); + } + } + } + + #region IOpcRequest Members + /// + /// An unique identifier, assigned by the client, for the request. + /// + public object Handle => m_requestHandle; + + #endregion + + #region IActualTime Members + /// + /// The actual start time used by a server while processing a request. + /// + public DateTime StartTime + { + get => m_startTime; + set => m_startTime = value; + } + + /// + /// The actual end time used by a server while processing a request. + /// + public DateTime EndTime + { + get => m_endTime; + set => m_endTime = value; + } + #endregion + + #region Private Methods + /// + /// Invokes callback for a data change update. + /// + private bool InvokeCallback(TsCHdaDataUpdateEventHandler callback, object results) + { + // check for valid result type. + if (!typeof(TsCHdaItemValueCollection[]).IsInstanceOfType(results)) + { + return false; + } + + var values = (TsCHdaItemValueCollection[])results; + + // update item handles and actual times. + UpdateResults(values); + + try + { + callback(this, values); + } + catch + { + // ignore exceptions in the callbacks. + } + + // request never completes. + return false; + } + + /// + /// Invokes callback for a read request. + /// + private bool InvokeCallback(TsCHdaReadValuesCompleteEventHandler callback, object results) + { + // check for valid result type. + if (!typeof(TsCHdaItemValueCollection[]).IsInstanceOfType(results)) + { + return false; + } + + var values = (TsCHdaItemValueCollection[])results; + + // update item handles and actual times. + UpdateResults(values); + + try + { + callback(this, values); + } + catch + { + // ignore exceptions in the callbacks. + } + + // check if all data has been sent. + foreach (var value in values) + { + if (value.Result == OpcResult.Hda.S_MOREDATA) + { + return false; + } + } + + // request is complete. + return true; + } + + /// + /// Invokes callback for a read attributes request. + /// + private bool InvokeCallback(TsCHdaReadAttributesCompleteEventHandler callback, object results) + { + // check for valid result type. + if (!typeof(TsCHdaItemAttributeCollection).IsInstanceOfType(results)) + { + return false; + } + + var values = (TsCHdaItemAttributeCollection)results; + + // update item handles and actual times. + UpdateResults(new TsCHdaItemAttributeCollection[] { values }); + + try + { + callback(this, values); + } + catch + { + // ignore exceptions in the callbacks. + } + + // request always completes + return true; + } + + /// + /// Invokes callback for a read annotations request. + /// + private bool InvokeCallback(TsCHdaReadAnnotationsCompleteEventHandler callback, object results) + { + // check for valid result type. + if (!typeof(TsCHdaAnnotationValueCollection[]).IsInstanceOfType(results)) + { + return false; + } + + var values = (TsCHdaAnnotationValueCollection[])results; + + // update item handles and actual times. + UpdateResults(values); + + try + { + callback(this, values); + } + catch + { + // ignore exceptions in the callbacks. + } + + // request always completes + return true; + } + + /// + /// Invokes callback for a read annotations request. + /// + private bool InvokeCallback(TsCHdaUpdateCompleteEventHandler callback, object results) + { + // check for valid result type. + if (!typeof(TsCHdaResultCollection[]).IsInstanceOfType(results)) + { + return false; + } + + var values = (TsCHdaResultCollection[])results; + + // update item handles and actual times. + UpdateResults(values); + + try + { + callback(this, values); + } + catch + { + // ignore exceptions in the callbacks. + } + + // request always completes + return true; + } + + /// + /// Updates the result objects with locally cached information. + /// + private void UpdateResults(OpcItem[] results) + { + foreach (var result in results) + { + // update actual times. + if (typeof(ITsCHdaActualTime).IsInstanceOfType(result)) + { + ((ITsCHdaActualTime)result).StartTime = StartTime; + ((ITsCHdaActualTime)result).EndTime = EndTime; + } + + // add item identifier to value collection. + var itemID = (OpcItem)m_items[result.ServerHandle]; + + if (itemID != null) + { + result.ItemName = itemID.ItemName; + result.ItemPath = itemID.ItemPath; + result.ServerHandle = itemID.ServerHandle; + result.ClientHandle = itemID.ClientHandle; + } + } + } + #endregion + + #region Private Members + private object m_requestHandle = null; + private Delegate m_callback = null; + private int m_requestID = 0; + private int m_cancelID = 0; + private DateTime m_startTime = DateTime.MinValue; + private DateTime m_endTime = DateTime.MinValue; + private Hashtable m_items = null; + private ArrayList m_results = null; + private event TsCHdaCancelCompleteEventHandler m_cancelCompleteEvent = null; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Hda/Result.cs b/Technosoftware/DaAeHdaClient.Com/Hda/Result.cs new file mode 100644 index 0000000..161935f --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Hda/Result.cs @@ -0,0 +1,68 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + namespace Hda + { + /// + /// Defines all well known COM HDA HRESULT codes. + /// + internal struct Result + { + /// + public const int E_MAXEXCEEDED = -0X3FFBEFFF; // 0xC0041001 + /// + public const int S_NODATA = +0x40041002; // 0x40041002 + /// + public const int S_MOREDATA = +0x40041003; // 0x40041003 + /// + public const int E_INVALIDAGGREGATE = -0X3FFBEFFC; // 0xC0041004 + /// + public const int S_CURRENTVALUE = +0x40041005; // 0x40041005 + /// + public const int S_EXTRADATA = +0x40041006; // 0x40041006 + /// + public const int W_NOFILTER = -0x7FFBEFF9; // 0x80041007 + /// + public const int E_UNKNOWNATTRID = -0x3FFBEFF8; // 0xC0041008 + /// + public const int E_NOT_AVAIL = -0x3FFBEFF7; // 0xC0041009 + /// + public const int E_INVALIDDATATYPE = -0x3FFBEFF6; // 0xC004100A + /// + public const int E_DATAEXISTS = -0x3FFBEFF5; // 0xC004100B + /// + public const int E_INVALIDATTRID = -0x3FFBEFF4; // 0xC004100C + /// + public const int E_NODATAEXISTS = -0x3FFBEFF3; // 0xC004100D + /// + public const int S_INSERTED = +0x4004100E; // 0x4004100E + /// + public const int S_REPLACED = +0x4004100F; // 0x4004100F + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Hda/Server.cs b/Technosoftware/DaAeHdaClient.Com/Hda/Server.cs new file mode 100644 index 0000000..8f763ae --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Hda/Server.cs @@ -0,0 +1,3473 @@ +#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; +using System.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Hda; +using Technosoftware.OpcRcw.Hda; + +#endregion + +#pragma warning disable CS0618 + +namespace Technosoftware.DaAeHdaClient.Com.Hda +{ + /// + /// An in-process wrapper for a remote OPC COM-HDA server (thread-safe). + /// + internal class Server : Technosoftware.DaAeHdaClient.Com.Server, ITsCHdaServer + { + #region Constructor + //====================================================================== + // Construction + + /// + /// Initializes the object. + /// + internal Server() { } + + /// + /// Initializes the object with the specifed COM server. + /// + internal Server(OpcUrl url, object server) + { + if (url == null) throw new ArgumentNullException(nameof(url)); + + url_ = (OpcUrl)url.Clone(); + server_ = server; + + // establish the callback. + Advise(); + } + #endregion + + #region IDisposable Members + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected override void Dispose(bool disposing) + { + if (!disposed_) + { + lock (lock_) + { + if (disposing) + { + // Release managed resources. + // close the callback. + Unadvise(); + } + + // Release unmanaged resources. + // Set large fields to null. + + disposed_ = true; + } + } + + base.Dispose(disposing); + } + #endregion + + #region Server Info + //====================================================================== + // GetStatus + + /// + /// Returns the current server status. + /// + /// The current server status. + public OpcServerStatus GetServerStatus() + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var pStatus = IntPtr.Zero; + + var wStatus = OPCHDA_SERVERSTATUS.OPCHDA_INDETERMINATE; + + var pftCurrentTime = IntPtr.Zero; + var pftStartTime = IntPtr.Zero; + short wMajorVersion = 0; + short wMinorVersion = 0; + short wBuildNumber = 0; + var dwMaxReturnValues = 0; + string szStatusString = null; + string szVendorInfo = null; + + // invoke COM method. + try + { + ((IOPCHDA_Server)server_).GetHistorianStatus( + out wStatus, + out pftCurrentTime, + out pftStartTime, + out wMajorVersion, + out wMinorVersion, + out wBuildNumber, + out dwMaxReturnValues, + out szStatusString, + out szVendorInfo); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Server.GetHistorianStatus", e); + } + + // unmarshal return parameters and free memory. + var status = new OpcServerStatus(); + + status.VendorInfo = szVendorInfo; + status.ProductVersion = string.Format("{0}.{1}.{2}", wMajorVersion, wMinorVersion, wBuildNumber); + switch (wStatus) + { + case OPCHDA_SERVERSTATUS.OPCHDA_DOWN: + status.ServerState = OpcServerState.NotOperational; + break; + case OPCHDA_SERVERSTATUS.OPCHDA_INDETERMINATE: + status.ServerState = OpcServerState.Unknown; + break; + case OPCHDA_SERVERSTATUS.OPCHDA_UP: + status.ServerState = OpcServerState.Operational; + break; + default: + status.ServerState = OpcServerState.Unknown; + break; + } + status.ServerState = (OpcServerState)wStatus; + status.StatusInfo = szStatusString; + status.StartTime = DateTime.MinValue; + status.CurrentTime = DateTime.MinValue; + status.MaxReturnValues = dwMaxReturnValues; + + if (pftStartTime != IntPtr.Zero) + { + status.StartTime = Utilities.Interop.GetDateTime(pftStartTime); + Marshal.FreeCoTaskMem(pftStartTime); + } + + if (pftCurrentTime != IntPtr.Zero) + { + status.CurrentTime = Utilities.Interop.GetDateTime(pftCurrentTime); + Marshal.FreeCoTaskMem(pftCurrentTime); + } + + return status; + } + } + + //====================================================================== + // GetAttributes + + /// + /// Returns the item attributes supported by the server. + /// + /// The a set of item attributes and their descriptions. + public TsCHdaAttribute[] GetAttributes() + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = 0; + + var pIDs = IntPtr.Zero; + var pNames = IntPtr.Zero; + var pDescriptions = IntPtr.Zero; + var pDataTypes = IntPtr.Zero; + + try + { + ((IOPCHDA_Server)server_).GetItemAttributes( + out count, + out pIDs, + out pNames, + out pDescriptions, + out pDataTypes); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Server.GetItemAttributes", e); + } + + // check if no attributes supported. + if (count == 0) + { + return new TsCHdaAttribute[0]; + } + + // unmarshal return parameters and free memory. + var ids = Utilities.Interop.GetInt32s(ref pIDs, count, true); + var names = Utilities.Interop.GetUnicodeStrings(ref pNames, count, true); + var descriptions = Utilities.Interop.GetUnicodeStrings(ref pDescriptions, count, true); + var datatypes = Utilities.Interop.GetInt16s(ref pDataTypes, count, true); + + // verify return parameters. + if (ids == null || names == null || descriptions == null || datatypes == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The response from the server was invalid or incomplete"); + } + + var attributes = new TsCHdaAttribute[count]; + + for (var ii = 0; ii < count; ii++) + { + attributes[ii] = new TsCHdaAttribute(); + + attributes[ii].ID = ids[ii]; + attributes[ii].Name = names[ii]; + attributes[ii].Description = descriptions[ii]; + attributes[ii].DataType = Utilities.Interop.GetType((VarEnum)Enum.ToObject(typeof(VarEnum), datatypes[ii])); + } + + // return results. + return attributes; + } + } + + //====================================================================== + // GetAggregates + + /// + /// Returns the aggregates supported by the server. + /// + /// The a set of aggregates and their descriptions. + public TsCHdaAggregate[] GetAggregates() + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = 0; + + var pIDs = IntPtr.Zero; + var pNames = IntPtr.Zero; + var pDescriptions = IntPtr.Zero; + + try + { + ((IOPCHDA_Server)server_).GetAggregates( + out count, + out pIDs, + out pNames, + out pDescriptions); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Server.GetAggregates", e); + } + + // check if no aggregates supported. + if (count == 0) + { + return new TsCHdaAggregate[0]; + } + + // unmarshal return parameters and free memory. + var ids = Utilities.Interop.GetInt32s(ref pIDs, count, true); + var names = Utilities.Interop.GetUnicodeStrings(ref pNames, count, true); + var descriptions = Utilities.Interop.GetUnicodeStrings(ref pDescriptions, count, true); + + // verify return parameters. + if (ids == null || names == null || descriptions == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The response from the server was invalid or incomplete"); + } + + var aggregates = new TsCHdaAggregate[count]; + + for (var ii = 0; ii < count; ii++) + { + aggregates[ii] = new TsCHdaAggregate(); + + aggregates[ii].Id = ids[ii]; + aggregates[ii].Name = names[ii]; + aggregates[ii].Description = descriptions[ii]; + } + + // return results. + return aggregates; + } + } + + //====================================================================== + // CreateBrowser + + /// + /// Creates a object used to browse the server address space. + /// + /// The set of attribute filters to use when browsing. + /// A result code for each individual filter. + /// A browser object that must be released by calling Dispose(). + public ITsCHdaBrowser CreateBrowser(TsCHdaBrowseFilter[] filters, out OpcResult[] results) + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // initialize arguments. + var count = (filters != null) ? filters.Length : 0; + + // marshal input parameters. + var ids = new int[count]; + var values = new object[count]; + var operators = new OPCHDA_OPERATORCODES[count]; + + for (var ii = 0; ii < count; ii++) + { + ids[ii] = filters[ii].AttributeID; + operators[ii] = (OPCHDA_OPERATORCODES)Enum.ToObject(typeof(OPCHDA_OPERATORCODES), filters[ii].Operator); + values[ii] = Utilities.Interop.GetVARIANT(filters[ii].FilterValue); + } + + // initialize output parameners + IOPCHDA_Browser pBrowser = null; + var pErrors = IntPtr.Zero; + + // call COM server. + try + { + ((IOPCHDA_Server)server_).CreateBrowse( + count, + ids, + operators, + values, + out pBrowser, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Server.CreateBrowse", e); + } + + // unmarshal return parameters and free memory. + var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true); + + // verify return parameters. + if ((count > 0 && errors == null) || pBrowser == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + results = new OpcResult[count]; + + for (var ii = 0; ii < count; ii++) + { + results[ii] = Utilities.Interop.GetResultId(errors[ii]); + } + + // return browser. + return new Browser(this, pBrowser, filters, results); + } + } + #endregion + + #region Item Management + //====================================================================== + // CreateItems + + /// + /// Creates a set of items. + /// + /// The identifiers for the items to create. + /// The results for each item containing the server handle and result code. + public OpcItemResult[] CreateItems(OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // initialize input parameters. + var itemIDs = new string[items.Length]; + var clientHandles = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + if (items[ii] != null) + { + itemIDs[ii] = items[ii].ItemName; + clientHandles[ii] = CreateHandle(); + } + } + + // initialize output arguments. + var pServerHandles = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_Server)server_).GetItemHandles( + items.Length, + itemIDs, + clientHandles, + out pServerHandles, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Server.GetItemHandles", e); + } + + // unmarshal return parameters and free memory. + var serverHandles = Utilities.Interop.GetInt32s(ref pServerHandles, items.Length, true); + var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); + + // verify return parameters. + if (serverHandles == null || errors == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < results.Length; ii++) + { + results[ii] = new OpcItemResult(items[ii]); + results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + + if (results[ii].Result.Succeeded()) + { + // cache item id locally to store remote server handle/local client handle mapping. + var itemID = new OpcItem(); + + itemID.ItemName = items[ii].ItemName; + itemID.ItemPath = items[ii].ItemPath; + itemID.ServerHandle = serverHandles[ii]; + itemID.ClientHandle = items[ii].ClientHandle; + + items_.Add(clientHandles[ii], itemID); + + // return correct handles in result. + results[ii].ServerHandle = clientHandles[ii]; + results[ii].ClientHandle = items[ii].ClientHandle; + } + } + + return results; + } + } + + //====================================================================== + // ReleaseItems + + /// + /// Releases a set of previously created items. + /// + /// The server handles for the items to release. + /// The results for each item containing the result code. + public OpcItemResult[] ReleaseItems(OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_Server)server_).ReleaseItemHandles( + items.Length, + serverHandles, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Server.ReleaseItemHandles", e); + } + + // unmarshal return parameters and free memory. + var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); + + // verify return parameters. + if (errors == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < results.Length; ii++) + { + results[ii] = new OpcItemResult(items[ii]); + results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + + if (results[ii].Result.Succeeded() && items[ii].ServerHandle != null) + { + // lookup locally cached item id. + var itemID = (OpcItem)items_[items[ii].ServerHandle]; + + // remove the locally cached item. + if (itemID != null) + { + results[ii].ItemName = itemID.ItemName; + results[ii].ItemPath = itemID.ItemPath; + results[ii].ClientHandle = itemID.ClientHandle; + + items_.Remove(items[ii].ServerHandle); + } + } + } + + // return results. + return results; + } + } + + //====================================================================== + // ValidateItems + + /// + /// Validates a set of items. + /// + /// The identifiers for the items to validate. + /// The results for each item containing the result code. + public OpcItemResult[] ValidateItems(OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // initialize input parameters. + var itemIDs = new string[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + if (items[ii] != null) + { + itemIDs[ii] = items[ii].ItemName; + } + } + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_Server)server_).ValidateItemIDs( + items.Length, + itemIDs, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Server.ValidateItemIDs", e); + } + + // unmarshal return parameters and free memory. + var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); + + // verify return parameters. + if (errors == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < results.Length; ii++) + { + results[ii] = new OpcItemResult(items[ii]); + results[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + } + + return results; + } + } + #endregion + + #region Read Raw + //====================================================================== + // ReadRaw + + /// + /// Reads raw (unprocessed) data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// Whether the bounding item values should be returned. + /// The set of items to read (must include the server handle). + /// A set of values, qualities and timestamps within the requested time range for each item. + public TsCHdaItemValueCollection[] ReadRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + bool includeBounds, + OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaItemValueCollection[0]; + } + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncRead)server_).ReadRaw( + ref pStartTime, + ref pEndTime, + maxValues, + (includeBounds) ? 1 : 0, + serverHandles.Length, + serverHandles, + out pValues, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadRaw", e); + } + + // unmarhal modified item structures. + var results = Interop.GetItemValueCollections(ref pValues, items.Length, true); + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // store actual items in result. + UpdateActualTimes(results, pStartTime, pEndTime); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to read raw data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// Whether the bounding item values should be returned. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] ReadRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + bool includeBounds, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).ReadRaw( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + maxValues, + (includeBounds) ? 1 : 0, + serverHandles.Length, + serverHandles, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadRaw", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return results; + } + + // store actual items in request object. + UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + /// + /// Requests that the server periodically send notifications when new data becomes available for a set of items. + /// + /// The beginning of the history period to read. + /// The frequency, in seconds, that the server should check for new data. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] AdviseRaw( + TsCHdaTime startTime, + decimal updateInterval, + OpcItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var ftUpdateInterval = Interop.GetFILETIME(updateInterval); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).AdviseRaw( + internalRequest.RequestID, + ref pStartTime, + ftUpdateInterval, + serverHandles.Length, + serverHandles, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.AdviseRaw", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // send callbacks for any data that has already arrived. + internalRequest.Update(cancelID, results); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + /// + /// Begins the playback raw data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The frequency, in seconds, that the server send data. + /// The duration, in seconds, of the timespan returned with each update. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] PlaybackRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + decimal updateInterval, + decimal playbackDuration, + OpcItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + var ftUpdateInterval = Interop.GetFILETIME(updateInterval); + var ftUpdateDuration = Interop.GetFILETIME(playbackDuration); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_Playback)server_).ReadRawWithUpdate( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + maxValues, + ftUpdateDuration, + ftUpdateInterval, + serverHandles.Length, + serverHandles, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Playback.ReadRawWithUpdate", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // send callbacks for any data that has already arrived. + internalRequest.Update(cancelID, results); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + #endregion + + #region Read Processed + + /// + /// Reads processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The set of items to read (must include the server handle). + /// A set of values, qualities and timestamps within the requested time range for each item. + public TsCHdaItemValueCollection[] ReadProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + TsCHdaItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaItemValueCollection[0]; + } + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + var aggregateIDs = GetAggregateIDs(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + var ftResampleInterval = Interop.GetFILETIME(resampleInterval); + + // initialize output arguments. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncRead)server_).ReadProcessed( + ref pStartTime, + ref pEndTime, + ftResampleInterval, + serverHandles.Length, + serverHandles, + aggregateIDs, + out pValues, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadProcessed", e); + } + + // unmarhal modified item structures. + var results = Interop.GetItemValueCollections(ref pValues, items.Length, true); + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // store actual items in result. + UpdateActualTimes(results, pStartTime, pEndTime); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to read processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] ReadProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + var aggregateIDs = GetAggregateIDs(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + var ftResampleInterval = Interop.GetFILETIME(resampleInterval); + + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).ReadProcessed( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + ftResampleInterval, + serverHandles.Length, + serverHandles, + aggregateIDs, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadProcessed", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return results; + } + + // store actual items in request object. + UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + /// + /// Requests that the server periodically send notifications when new data becomes available for a set of items. + /// + /// The beginning of the history period to read. + /// The interval between returned values. + /// The number of resample intervals that the server should return in each callback. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] AdviseProcessed( + TsCHdaTime startTime, + decimal resampleInterval, + int numberOfIntervals, + TsCHdaItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + var aggregateIDs = GetAggregateIDs(items); + + var pStartTime = Interop.GetTime(startTime); + var ftResampleInterval = Interop.GetFILETIME(resampleInterval); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).AdviseProcessed( + internalRequest.RequestID, + ref pStartTime, + ftResampleInterval, + serverHandles.Length, + serverHandles, + aggregateIDs, + numberOfIntervals, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.AdviseProcessed", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // send callbacks for any data that has already arrived. + internalRequest.Update(cancelID, results); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + /// + /// Begins the playback of processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The number of resample intervals that the server should return in each callback. + /// The frequency, in seconds, that the server send data. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] PlaybackProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + int numberOfIntervals, + decimal updateInterval, + TsCHdaItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + var aggregateIDs = GetAggregateIDs(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + var ftResampleInterval = Interop.GetFILETIME(resampleInterval); + var ftUpdateInterval = Interop.GetFILETIME(updateInterval); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_Playback)server_).ReadProcessedWithUpdate( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + ftResampleInterval, + numberOfIntervals, + ftUpdateInterval, + serverHandles.Length, + serverHandles, + aggregateIDs, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_Playback.ReadProcessedWithUpdate", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // send callbacks for any data that has already arrived. + internalRequest.Update(cancelID, results); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + #endregion + + #region Read At Time + + /// + /// Reads data from the historian database for a set of items at specific times. + /// + /// The set of timestamps to use when reading items values. + /// The set of items to read (must include the server handle). + /// A set of values, qualities and timestamps within the requested time range for each item. + public TsCHdaItemValueCollection[] ReadAtTime(DateTime[] timestamps, OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaItemValueCollection[0]; + } + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // initialize output arguments. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncRead)server_).ReadAtTime( + ftTimestamps.Length, + ftTimestamps, + serverHandles.Length, + serverHandles, + out pValues, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadAtTime", e); + } + + // unmarhal modified item structures. + var results = Interop.GetItemValueCollections(ref pValues, items.Length, true); + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to read item values at specific times. + /// + /// The set of timestamps to use when reading items values. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] ReadAtTime( + DateTime[] timestamps, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).ReadAtTime( + internalRequest.RequestID, + ftTimestamps.Length, + ftTimestamps, + serverHandles.Length, + serverHandles, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadAtTime", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return results; + } + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + #endregion + + #region Read Modified + + /// + /// Reads item values that have been deleted or replaced. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The set of items to read (must include the server handle). + /// A set of values, qualities and timestamps within the requested time range for each item. + public TsCHdaModifiedValueCollection[] ReadModified( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaModifiedValueCollection[0]; + } + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncRead)server_).ReadModified( + ref pStartTime, + ref pEndTime, + maxValues, + serverHandles.Length, + serverHandles, + out pValues, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadModified", e); + } + + // unmarhal modified item structures. + var results = Interop.GetModifiedValueCollections(ref pValues, items.Length, true); + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // store actual items in result. + UpdateActualTimes(results, pStartTime, pEndTime); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to read item values that have been deleted or replaced. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] ReadModified( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).ReadModified( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + maxValues, + serverHandles.Length, + serverHandles, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadModified", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return results; + } + + // store actual items in request object. + UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + #endregion + + #region Read Attributes + + /// + /// Reads the current or historical values for the attributes of an item. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The item to read (must include the server handle). + /// The attributes to read. + /// A set of attribute values for each requested attribute. + public TsCHdaItemAttributeCollection ReadAttributes( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem item, + int[] attributeIDs) + { + if (item == null) throw new ArgumentNullException(nameof(item)); + if (attributeIDs == null) throw new ArgumentNullException(nameof(attributeIDs)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (attributeIDs.Length == 0) + { + return new TsCHdaItemAttributeCollection(item); + } + + // initialize input parameters. + var serverHandles = GetServerHandles(new OpcItem[] { item }); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncRead)server_).ReadAttribute( + ref pStartTime, + ref pEndTime, + serverHandles[0], + attributeIDs.Length, + attributeIDs, + out pValues, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncRead.ReadAttribute", e); + } + + // unmarhal item attribute structures. + var attributes = Interop.GetAttributeValueCollections(ref pValues, attributeIDs.Length, true); + + // create item level result collection. + var result = UpdateResults(item, attributes, ref pErrors); + + // store actual items in result. + UpdateActualTimes(new ITsCHdaActualTime[] { result }, pStartTime, pEndTime); + + // completed successfully. + return result; + } + } + + /// + /// Sends an asynchronous request to read the attributes of an item. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The item to read (must include the server handle). + /// The attributes to read. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the attribute ids. + public TsCHdaResultCollection ReadAttributes( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem item, + int[] attributeIDs, + object requestHandle, + TsCHdaReadAttributesCompleteEventHandler callback, + out IOpcRequest request) + { + if (item == null) throw new ArgumentNullException(nameof(item)); + if (attributeIDs == null) throw new ArgumentNullException(nameof(attributeIDs)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (attributeIDs.Length == 0) + { + return new TsCHdaResultCollection(); + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(new OpcItem[] { item }); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).ReadAttribute( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + serverHandles[0], + attributeIDs.Length, + attributeIDs, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.ReadAttribute", e); + } + + // create result objects. + var results = new TsCHdaResultCollection(item); + + // update result with error code and info from the item argument. + UpdateResult(item, results, 0); + + // unmarshal return parameters and free memory. + var errors = Utilities.Interop.GetInt32s(ref pErrors, attributeIDs.Length, true); + + // verify return parameters. + if (errors == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + // add results for each attribute. + foreach (var error in errors) + { + var result = new TsCHdaResult(Utilities.Interop.GetResultId(error)); + results.Add(result); + } + + // check if request has already completed. + if (internalRequest.Update(cancelID, new TsCHdaResultCollection[] { results })) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return results; + } + + // store actual items in request object. + UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + #endregion + + #region Annotations + + /// + /// Reads any annotations for an item within the a time interval. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The set of items to read (must include the server handle). + /// A set of annotations within the requested time range for each item. + public TsCHdaAnnotationValueCollection[] ReadAnnotations( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaAnnotationValueCollection[0]; + } + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pValues = IntPtr.Zero; + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncAnnotations)server_).Read( + ref pStartTime, + ref pEndTime, + serverHandles.Length, + serverHandles, + out pValues, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncAnnotations.Read", e); + } + + // unmarhal modified item structures. + var results = Interop.GetAnnotationValueCollections(ref pValues, items.Length, true); + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // store actual items in result. + UpdateActualTimes(results, pStartTime, pEndTime); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to read the annotations for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The set of items to read (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] ReadAnnotations( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items, + object requestHandle, + TsCHdaReadAnnotationsCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncAnnotations)server_).Read( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + serverHandles.Length, + serverHandles, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncAnnotations.Read", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return results; + } + + // store actual items in request object. + UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + /// + /// Inserts annotations for one or more items. + /// + /// A list of annotations to add for each item (must include the server handle). + /// The results of the insert operation for each annotation set. + public TsCHdaResultCollection[] InsertAnnotations(TsCHdaAnnotationValueCollection[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaResultCollection[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + OPCHDA_ANNOTATION[] pAnnotations = null; + OPCHDA_FILETIME[] pTimestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalAnnotatations( + items, + ref serverHandles, + ref pTimestamps, + ref pAnnotations); + + // handle trivial case. + if (count == 0) + { + return results; + } + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncAnnotations)server_).Insert( + serverHandles.Length, + serverHandles, + pTimestamps, + pAnnotations, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncAnnotations.Insert", e); + } + + // free memory allocated for input arguments. + for (var ii = 0; ii < pAnnotations.Length; ii++) + { + Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftTimeStamps, 1, true); + Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szAnnotation, 1, true); + Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftAnnotationTime, 1, true); + Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szUser, 1, true); + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to inserts annotations for one or more items. + /// + /// A list of annotations to add for each item (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] InsertAnnotations( + TsCHdaAnnotationValueCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + OPCHDA_ANNOTATION[] pAnnotations = null; + OPCHDA_FILETIME[] pTimestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalAnnotatations( + items, + ref serverHandles, + ref pTimestamps, + ref pAnnotations); + + // handle trivial case. + if (count == 0) + { + return GetIdentifiedResults(results); + } + + // create request. + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + var cancelID = 0; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncAnnotations)server_).Insert( + internalRequest.RequestID, + serverHandles.Length, + serverHandles, + pTimestamps, + pAnnotations, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncAnnotations.Insert", e); + } + + // free memory allocated for input arguments. + for (var ii = 0; ii < pAnnotations.Length; ii++) + { + Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftTimeStamps, 1, true); + Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szAnnotation, 1, true); + Utilities.Interop.GetDateTimes(ref pAnnotations[ii].ftAnnotationTime, 1, true); + Utilities.Interop.GetUnicodeStrings(ref pAnnotations[ii].szUser, 1, true); + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return GetIdentifiedResults(results); + } + + // return request object. + request = internalRequest; + + // completed successfully. + return GetIdentifiedResults(results); + } + } + + #endregion + + #region Insert/Replace + + /// + /// Inserts the values into the history database for one or more items. + /// + /// The set of values to insert. + /// Whether existing values should be replaced. + /// + public TsCHdaResultCollection[] Insert(TsCHdaItemValueCollection[] items, bool replace) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaResultCollection[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + object[] values = null; + int[] qualities = null; + DateTime[] timestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalValues( + items, + ref serverHandles, + ref values, + ref qualities, + ref timestamps); + + // handle trivial case. + if (count == 0) + { + return results; + } + + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + if (replace) + { + try + { + ((IOPCHDA_SyncUpdate)server_).InsertReplace( + serverHandles.Length, + serverHandles, + ftTimestamps, + values, + qualities, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.InsertReplace", e); + } + } + else + { + try + { + ((IOPCHDA_SyncUpdate)server_).Insert( + serverHandles.Length, + serverHandles, + ftTimestamps, + values, + qualities, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.Insert", e); + } + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to inserts values for one or more items. + /// + /// The set of values to insert. + /// Whether existing values should be replaced. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Insert( + TsCHdaItemValueCollection[] items, + bool replace, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + object[] values = null; + int[] qualities = null; + DateTime[] timestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalValues( + items, + ref serverHandles, + ref values, + ref qualities, + ref timestamps); + + // handle trivial case. + if (count == 0) + { + return GetIdentifiedResults(results); + } + + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // create request. + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + var cancelID = 0; + + // invoke COM method. + if (replace) + { + try + { + ((IOPCHDA_AsyncUpdate)server_).InsertReplace( + internalRequest.RequestID, + serverHandles.Length, + serverHandles, + ftTimestamps, + values, + qualities, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.InsertReplace", e); + } + } + else + { + try + { + ((IOPCHDA_AsyncUpdate)server_).Insert( + internalRequest.RequestID, + serverHandles.Length, + serverHandles, + ftTimestamps, + values, + qualities, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.Insert", e); + } + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return GetIdentifiedResults(results); + } + + // return request object. + request = internalRequest; + + // completed successfully. + return GetIdentifiedResults(results); + } + } + + /// + /// Replace the values into the history database for one or more items. + /// + /// The set of values to replace. + /// + public TsCHdaResultCollection[] Replace(TsCHdaItemValueCollection[] items) + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaResultCollection[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + object[] values = null; + int[] qualities = null; + DateTime[] timestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalValues( + items, + ref serverHandles, + ref values, + ref qualities, + ref timestamps); + + // handle trivial case. + if (count == 0) + { + return results; + } + + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncUpdate)server_).Replace( + serverHandles.Length, + serverHandles, + ftTimestamps, + values, + qualities, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.Replace", e); + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to replace values for one or more items. + /// + /// The set of values to replace. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Replace( + TsCHdaItemValueCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + object[] values = null; + int[] qualities = null; + DateTime[] timestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalValues( + items, + ref serverHandles, + ref values, + ref qualities, + ref timestamps); + + // handle trivial case. + if (count == 0) + { + return GetIdentifiedResults(results); + } + + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // create request. + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + var cancelID = 0; + + try + { + ((IOPCHDA_AsyncUpdate)server_).Replace( + internalRequest.RequestID, + serverHandles.Length, + serverHandles, + ftTimestamps, + values, + qualities, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.Replace", e); + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return GetIdentifiedResults(results); + } + + // return request object. + request = internalRequest; + + // completed successfully. + return GetIdentifiedResults(results); + } + } + + #endregion + + #region Delete + + /// + /// Deletes the values with the specified time domain for one or more items. + /// + /// The beginning of the history period to delete. + /// The end of the history period to be delete. + /// The set of items to delete (must include the server handle). + /// The results of the delete operation for each item. + public OpcItemResult[] Delete( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncUpdate)server_).DeleteRaw( + ref pStartTime, + ref pEndTime, + serverHandles.Length, + serverHandles, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.DeleteRaw", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to delete values for one or more items. + /// + /// The beginning of the history period to delete. + /// The end of the history period to be delete. + /// The set of items to delete (must include the server handle). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Delete( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize input parameters. + var requestID = internalRequest.RequestID; + var cancelID = 0; + + var serverHandles = GetServerHandles(items); + + var pStartTime = Interop.GetTime(startTime); + var pEndTime = Interop.GetTime(endTime); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_AsyncUpdate)server_).DeleteRaw( + internalRequest.RequestID, + ref pStartTime, + ref pEndTime, + serverHandles.Length, + serverHandles, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.DeleteRaw", e); + } + + // create result objects. + var results = new OpcItemResult[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new OpcItemResult(); + } + + // update result with error code and info from the item argument. + UpdateResults(items, results, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return results; + } + + // store actual items in request object. + UpdateActualTimes(new ITsCHdaActualTime[] { internalRequest }, pStartTime, pEndTime); + + // return request object. + request = internalRequest; + + // completed successfully. + return results; + } + } + + /// + /// Deletes the values at the specified times for one or more items. + /// + /// The set of timestamps to delete for one or more items. + /// The results of the operation for each timestamp. + public TsCHdaResultCollection[] DeleteAtTime(TsCHdaItemTimeCollection[] items) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new TsCHdaResultCollection[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + DateTime[] timestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalTimestamps( + items, + ref serverHandles, + ref timestamps); + + // handle trivial case. + if (count == 0) + { + return results; + } + + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + // invoke COM method. + try + { + ((IOPCHDA_SyncUpdate)server_).DeleteAtTime( + serverHandles.Length, + serverHandles, + ftTimestamps, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_SyncUpdate.DeleteAtTime", e); + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // completed successfully. + return results; + } + } + + /// + /// Sends an asynchronous request to delete values for one or more items at a specified times. + /// + /// The set of timestamps to delete for one or more items. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] DeleteAtTime( + TsCHdaItemTimeCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + if (items == null) throw new ArgumentNullException(nameof(items)); + + request = null; + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + // handle trivial case. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // create empty set of result collections. + var results = CreateResultCollections(items); + + // initialize input parameters. + int[] serverHandles = null; + DateTime[] timestamps = null; + + // flatten out list of collections into a set of single arrays. + var count = MarshalTimestamps( + items, + ref serverHandles, + ref timestamps); + + // handle trivial case. + if (count == 0) + { + return GetIdentifiedResults(results); + } + + var ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // create request. + var internalRequest = callback_.CreateRequest(requestHandle, callback); + + // initialize output arguments. + var pErrors = IntPtr.Zero; + + var cancelID = 0; + + try + { + ((IOPCHDA_AsyncUpdate)server_).DeleteAtTime( + internalRequest.RequestID, + serverHandles.Length, + serverHandles, + ftTimestamps, + out cancelID, + out pErrors); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncUpdate.DeleteAtTime", e); + } + + // unmarshal return parameters and free memory. + UpdateResults(items, results, count, ref pErrors); + + // check if request has already completed. + if (internalRequest.Update(cancelID, results)) + { + // discard the request. + request = null; + callback_.CancelRequest(internalRequest, (TsCHdaCancelCompleteEventHandler)null); + + // return results. + return GetIdentifiedResults(results); + } + + // return request object. + request = internalRequest; + + // completed successfully. + return GetIdentifiedResults(results); + } + } + + #endregion + + #region Cancel + //====================================================================== + // CancelRequest + + /// + /// Cancels an asynchronous request. + /// + /// The state object for the request to cancel. + public void CancelRequest(IOpcRequest request) + { + CancelRequest(request, (TsCHdaCancelCompleteEventHandler)null); + } + + /// + /// Cancels an asynchronous request. + /// + /// The state object for the request to cancel. + /// A delegate used to receive notifications when the request completes. + public void CancelRequest(IOpcRequest request, TsCHdaCancelCompleteEventHandler callback) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + var internalRequest = (Request)request; + + // register the cancel request callback. + callback_.CancelRequest(internalRequest, callback); + + // invoke COM method. + try + { + ((IOPCHDA_AsyncRead)server_).Cancel(internalRequest.CancelID); + } + catch (Exception e) + { + // a return code of E_FAIL indicates the request does not exist or can't be cancelled. + if (OpcResult.E_FAIL.Code != Marshal.GetHRForException(e)) + { + throw Utilities.Interop.CreateException("IOPCHDA_AsyncRead.Cancel", e); + } + } + } + } + + #endregion + + #region Private Methods + /// + /// Establishes a connection point callback with the COM server. + /// + private void Advise() + { + if (connection_ == null) + { + try + { + connection_ = new ConnectionPoint(server_, typeof(IOPCHDA_DataCallback).GUID); + connection_.Advise(callback_); + } + catch + { + connection_ = null; + } + } + } + + /// + /// Closes a connection point callback with the COM server. + /// + private void Unadvise() + { + if (connection_ != null) + { + if (connection_.Unadvise() == 0) + { + connection_.Dispose(); + connection_ = null; + } + } + } + + /// + /// Creates a unique handle for an item. + /// + private int CreateHandle() + { + return nextHandle_++; + } + + /// + /// Finds an invalid server handle. + /// + private int GetInvalidHandle() + { + var max = 0; + + foreach (OpcItem item in items_.Values) + { + var handle = (int)item.ServerHandle; + + if (max < handle) + { + max = handle; + } + } + + return max + 1; + } + + /// + /// Gets the total count for multiple collections. + /// + private int GetCount(ICollection[] collections) + { + var count = 0; + + if (collections != null) + { + foreach (var collection in collections) + { + if (collection != null) + { + count += collection.Count; + } + } + } + + return count; + } + + /// + /// Initializes a set of result collections from a set of item ids. + /// + TsCHdaResultCollection[] CreateResultCollections(OpcItem[] items) + { + TsCHdaResultCollection[] results = null; + + if (items != null) + { + results = new TsCHdaResultCollection[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + results[ii] = new TsCHdaResultCollection(); + + if (items[ii] != null) + { + UpdateResult(items[ii], results[ii], 0); + } + } + } + + return results; + } + + /// + /// Returns an array of item server handles. + /// + private int[] GetServerHandles(OpcItem[] items) + { + // use this if the client passes an unrecognized server handle. + var invalidHandle = GetInvalidHandle(); + + // create server handle array. + var serverHandles = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + serverHandles[ii] = invalidHandle; + + if (items[ii] != null && items[ii].ServerHandle != null) + { + // lookup cached handle. + var item = (OpcItem)items_[items[ii].ServerHandle]; + + if (item != null) + { + serverHandles[ii] = (int)item.ServerHandle; + } + } + } + + // return handles. + return serverHandles; + } + + /// + /// Returns an array of item aggregate ids. + /// + private int[] GetAggregateIDs(TsCHdaItem[] items) + { + var aggregateIDs = new int[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + aggregateIDs[ii] = 0; + + if (items[ii].Aggregate != TsCHdaAggregateID.NoAggregate) + { + aggregateIDs[ii] = items[ii].Aggregate; + } + } + + return aggregateIDs; + } + + /// + /// Updates the result with locally cached item information. + /// + void UpdateResult(OpcItem item, OpcItem result, int error) + { + result.ItemName = item.ItemName; + result.ItemPath = item.ItemPath; + result.ClientHandle = item.ClientHandle; + result.ServerHandle = item.ServerHandle; + + if (error >= 0 && item.ServerHandle != null) + { + // lookup locally cached item id. + var itemID = (OpcItem)items_[item.ServerHandle]; + + // update result with locally cached information. + if (itemID != null) + { + result.ItemName = itemID.ItemName; + result.ItemPath = itemID.ItemPath; + result.ClientHandle = itemID.ClientHandle; + } + } + } + + /// + /// Adds the actual start/end times to a result collection. + /// + void UpdateActualTimes( + ITsCHdaActualTime[] results, + OPCHDA_TIME pStartTime, + OPCHDA_TIME pEndTime) + { + // unmarshal actual times from input arguments. + var startTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Interop.Convert(pStartTime.ftTime)); + var endTime = Technosoftware.DaAeHdaClient.Com.Interop.GetFILETIME(Interop.Convert(pEndTime.ftTime)); + + foreach (var result in results) + { + result.StartTime = startTime; + result.EndTime = endTime; + } + } + + /// + /// Updates the attribute value objects before returing them to the client. + /// + TsCHdaItemAttributeCollection UpdateResults( + OpcItem item, + TsCHdaAttributeValueCollection[] attributes, + ref IntPtr pErrors) + { + // unmarshal return parameters and free memory. + var errors = Utilities.Interop.GetInt32s(ref pErrors, attributes.Length, true); + + // verify return parameters. + if (attributes == null || errors == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + // set attribute level errors. + for (var ii = 0; ii < attributes.Length; ii++) + { + attributes[ii].Result = Utilities.Interop.GetResultId(errors[ii]); + } + + // create item level collection. + var result = new TsCHdaItemAttributeCollection(); + + foreach (var attribute in attributes) + { + result.Add(attribute); + } + + // add locally cached item information. + UpdateResult(item, result, 0); + + // all done. + return result; + } + + /// + /// Updates the annotation value objects before returing them to the client. + /// + void UpdateResults( + OpcItem[] items, + OpcItem[] results, + ref IntPtr pErrors) + { + // unmarshal return parameters and free memory. + var errors = Utilities.Interop.GetInt32s(ref pErrors, items.Length, true); + + // verify return parameters. + if (results == null || errors == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + for (var ii = 0; ii < results.Length; ii++) + { + // get cached item information. + UpdateResult(items[ii], results[ii], errors[ii]); + + // lookup the error code. + if (typeof(IOpcResult).IsInstanceOfType(results[ii])) + { + ((IOpcResult)results[ii]).Result = Utilities.Interop.GetResultId(errors[ii]); + } + } + } + + /// + /// Unmarshals the errors array and updates the result objects. + /// + void UpdateResults(ICollection[] items, TsCHdaResultCollection[] results, int count, ref IntPtr pErrors) + { + // unmarshal return parameters and free memory. + var errors = Utilities.Interop.GetInt32s(ref pErrors, count, true); + + // verify return parameters. + if (errors == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + // create result object and lookup error code. + var index = 0; + + for (var ii = 0; ii < items.Length; ii++) + { + for (var jj = 0; jj < items[ii].Count; jj++) + { + if (index >= count) + { + break; + } + + var result = new TsCHdaResult(Utilities.Interop.GetResultId(errors[index++])); + results[ii].Add(result); + } + } + } + + /// + /// Flattens a set of item value collections into an set of single arrays. + /// + private int MarshalValues( + TsCHdaItemValueCollection[] items, + ref int[] handles, + ref object[] values, + ref int[] qualities, + ref DateTime[] timestamps) + { + + // determine the total length. + var count = GetCount(items); + + // flatten out list of collections into a set of single arrays. + handles = new int[count]; + timestamps = new DateTime[count]; + values = new object[count]; + qualities = new int[count]; + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + + var index = 0; + + for (var ii = 0; ii < items.Length; ii++) + { + foreach (TsCHdaItemValue value in items[ii]) + { + handles[index] = serverHandles[ii]; + timestamps[index] = value.Timestamp; + values[index] = Utilities.Interop.GetVARIANT(value.Value); + qualities[index] = value.Quality.GetCode(); + + index++; + } + } + + // return the total count. + return count; + } + + /// + /// Flattens a set of item time collections into an set of single arrays. + /// + private int MarshalTimestamps( + TsCHdaItemTimeCollection[] items, + ref int[] handles, + ref DateTime[] timestamps) + { + + // determine the total length. + var count = GetCount(items); + + // flatten out list of collections into a set of single arrays. + handles = new int[count]; + timestamps = new DateTime[count]; + + // initialize input parameters. + var serverHandles = GetServerHandles(items); + + var index = 0; + + for (var ii = 0; ii < items.Length; ii++) + { + foreach (DateTime value in items[ii]) + { + handles[index] = serverHandles[ii]; + timestamps[index] = value; + + index++; + } + } + + // return the total count. + return count; + } + + /// + /// Marshals a set of annotation collections into an set of arrays. + /// + private int MarshalAnnotatations( + TsCHdaAnnotationValueCollection[] items, + ref int[] serverHandles, + ref OPCHDA_FILETIME[] ftTimestamps, + ref OPCHDA_ANNOTATION[] annotations) + { + // determine the total length. + var count = GetCount(items); + + // fetch item server handles. + var remoteHandles = GetServerHandles(items); + + // allocate input arrays. + serverHandles = new int[count]; + annotations = new OPCHDA_ANNOTATION[count]; + + var timestamps = new DateTime[count]; + + // flatten array of collections into a single array. + var index = 0; + + for (var ii = 0; ii < items.Length; ii++) + { + for (var jj = 0; jj < items[ii].Count; jj++) + { + serverHandles[index] = remoteHandles[ii]; + timestamps[index] = items[ii][jj].Timestamp; + + annotations[index] = new OPCHDA_ANNOTATION(); + + annotations[index].dwNumValues = 1; + annotations[index].ftTimeStamps = Utilities.Interop.GetFILETIMEs(new DateTime[] { timestamps[jj] }); + annotations[index].szAnnotation = Utilities.Interop.GetUnicodeStrings(new string[] { items[ii][jj].Annotation }); + annotations[index].ftAnnotationTime = Utilities.Interop.GetFILETIMEs(new DateTime[] { items[ii][jj].CreationTime }); + annotations[index].szUser = Utilities.Interop.GetUnicodeStrings(new string[] { items[ii][jj].User }); + + index++; + } + } + + ftTimestamps = Interop.GetFILETIMEs(timestamps); + + // return the total number of annotations. + return count; + } + + /// + /// Collapses a set of result collections into a single result code. + /// + private OpcItemResult[] GetIdentifiedResults(TsCHdaResultCollection[] results) + { + // handle trival case. + if (results == null || results.Length == 0) + { + return new OpcItemResult[0]; + } + + // fetch the results from each collection. + var items = new OpcItemResult[results.Length]; + + for (var ii = 0; ii < results.Length; ii++) + { + items[ii] = new OpcItemResult(results[ii]); + + // check if data actually exists. + if (results[ii] == null || results[ii].Count == 0) + { + items[ii].Result = OpcResult.Hda.S_NODATA; + continue; + } + + // start with the first result code. + var resultID = results[ii][0].Result; + + foreach (TsCHdaResult result in results[ii]) + { + // all result in the collection should have the same error. + if (resultID.Code != result.Result.Code) + { + resultID = OpcResult.E_FAIL; + break; + } + } + } + + // all done. + return items; + } + #endregion + + #region Private Members + private static int nextHandle_ = 1; + private Hashtable items_ = new Hashtable(); + private DataCallback callback_ = new DataCallback(); + private ConnectionPoint connection_; + + /// + /// The synchronization object for subscription access + /// + private static volatile object lock_ = new object(); + + private bool disposed_ = false; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Interop.cs b/Technosoftware/DaAeHdaClient.Com/Interop.cs new file mode 100644 index 0000000..fadc18e --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Interop.cs @@ -0,0 +1,1515 @@ +#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.Net; +using System.Globalization; +using System.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Da; +using Technosoftware.DaAeHdaClient.Com.Utilities; +#endregion + +#pragma warning disable 0618 + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// Exposes WIN32 and COM API functions. + /// + public class Interop + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct SERVER_INFO_100 + { + public uint sv100_platform_id; + [MarshalAs(UnmanagedType.LPWStr)] + public string sv100_name; + } + + private const uint LEVEL_SERVER_INFO_100 = 100; + private const uint LEVEL_SERVER_INFO_101 = 101; + + private const int MAX_PREFERRED_LENGTH = -1; + + private const uint SV_TYPE_WORKSTATION = 0x00000001; + private const uint SV_TYPE_SERVER = 0x00000002; + + [DllImport("Netapi32.dll")] + private static extern int NetServerEnum( + IntPtr servername, + uint level, + out IntPtr bufptr, + int prefmaxlen, + out int entriesread, + out int totalentries, + uint servertype, + IntPtr domain, + IntPtr resume_handle); + + [DllImport("Netapi32.dll")] + private static extern int NetApiBufferFree(IntPtr buffer); + + /// + /// Enumerates computers on the local network. + /// + public static string[] EnumComputers() + { + IntPtr pInfo; + int entriesRead; + int totalEntries; + var result = NetServerEnum( + IntPtr.Zero, + LEVEL_SERVER_INFO_100, + out pInfo, + MAX_PREFERRED_LENGTH, + out entriesRead, + out totalEntries, + SV_TYPE_WORKSTATION | SV_TYPE_SERVER, + IntPtr.Zero, + IntPtr.Zero); + + if (result != 0) + { + throw new ApplicationException("NetApi Error = " + string.Format("0x{0,0:X}", result)); + } + + var computers = new string[entriesRead]; + + var pos = pInfo; + + for (var ii = 0; ii < entriesRead; ii++) + { + var info = (SERVER_INFO_100)Marshal.PtrToStructure(pos, typeof(SERVER_INFO_100)); + + computers[ii] = info.sv100_name; + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(SERVER_INFO_100))); + } + + NetApiBufferFree(pInfo); + + return computers; + } + + private const int MAX_MESSAGE_LENGTH = 1024; + + private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + [DllImport("Kernel32.dll")] + private static extern int FormatMessageW( + int dwFlags, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + IntPtr lpBuffer, + int nSize, + IntPtr Arguments); + + /// + /// Retrieves the system message text for the specified error. + /// + public static string GetSystemMessage(int error) + { + var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH); + + var result = FormatMessageW( + (int)(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_SYSTEM), + IntPtr.Zero, + error, + 0, + buffer, + MAX_MESSAGE_LENGTH - 1, + IntPtr.Zero); + + var msg = Marshal.PtrToStringUni(buffer); + Marshal.FreeCoTaskMem(buffer); + + if (msg != null && msg.Length > 0) + { + return msg; + } + + return string.Format("0x{0,0:X}", error); + } + + private const int MAX_COMPUTERNAME_LENGTH = 31; + + [DllImport("Kernel32.dll")] + private static extern int GetComputerNameW(IntPtr lpBuffer, ref int lpnSize); + + /// + /// Retrieves the name of the local computer. + /// + public static string GetComputerName() + { + string name = null; + var size = MAX_COMPUTERNAME_LENGTH + 1; + + var pName = Marshal.AllocCoTaskMem(size * 2); + + if (GetComputerNameW(pName, ref size) != 0) + { + name = Marshal.PtrToStringUni(pName, size); + } + + Marshal.FreeCoTaskMem(pName); + + return name; + } + + #region OLE32 Function/Interface Declarations + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct SOLE_AUTHENTICATION_SERVICE + { + public uint dwAuthnSvc; + public uint dwAuthzSvc; + [MarshalAs(UnmanagedType.LPWStr)] + public string pPrincipalName; + public int hr; + } + + private const uint RPC_C_AUTHN_NONE = 0; + private const uint RPC_C_AUTHN_DCE_PRIVATE = 1; + private const uint RPC_C_AUTHN_DCE_PUBLIC = 2; + private const uint RPC_C_AUTHN_DEC_PUBLIC = 4; + private const uint RPC_C_AUTHN_GSS_NEGOTIATE = 9; + private const uint RPC_C_AUTHN_WINNT = 10; + private const uint RPC_C_AUTHN_GSS_SCHANNEL = 14; + private const uint RPC_C_AUTHN_GSS_KERBEROS = 16; + private const uint RPC_C_AUTHN_DPA = 17; + private const uint RPC_C_AUTHN_MSN = 18; + private const uint RPC_C_AUTHN_DIGEST = 21; + private const uint RPC_C_AUTHN_MQ = 100; + private const uint RPC_C_AUTHN_DEFAULT = 0xFFFFFFFF; + + private const uint RPC_C_AUTHZ_NONE = 0; + private const uint RPC_C_AUTHZ_NAME = 1; + private const uint RPC_C_AUTHZ_DCE = 2; + private const uint RPC_C_AUTHZ_DEFAULT = 0xffffffff; + + private const uint RPC_C_AUTHN_LEVEL_DEFAULT = 0; + private const uint RPC_C_AUTHN_LEVEL_NONE = 1; + private const uint RPC_C_AUTHN_LEVEL_CONNECT = 2; + private const uint RPC_C_AUTHN_LEVEL_CALL = 3; + private const uint RPC_C_AUTHN_LEVEL_PKT = 4; + private const uint RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5; + private const uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; + + private const uint RPC_C_IMP_LEVEL_ANONYMOUS = 1; + private const uint RPC_C_IMP_LEVEL_IDENTIFY = 2; + private const uint RPC_C_IMP_LEVEL_IMPERSONATE = 3; + private const uint RPC_C_IMP_LEVEL_DELEGATE = 4; + + private const uint EOAC_NONE = 0x00; + private const uint EOAC_MUTUAL_AUTH = 0x01; + private const uint EOAC_CLOAKING = 0x10; + private const uint EOAC_SECURE_REFS = 0x02; + private const uint EOAC_ACCESS_CONTROL = 0x04; + private const uint EOAC_APPID = 0x08; + + private enum COINIT : uint //tagCOINIT + { + COINIT_MULTITHREADED = 0x0, //Initializes the thread for multi-threaded object concurrency. + COINIT_APARTMENTTHREADED = 0x2, //Initializes the thread for apartment-threaded object concurrency + COINIT_DISABLE_OLE1DDE = 0x4, //Disables DDE for OLE1 support + COINIT_SPEED_OVER_MEMORY = 0x8, //Trade memory for speed + } + + /// If function succeeds, it returns 0(S_OK). Otherwise, it returns an error code. + [DllImport("ole32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] + private static extern int CoInitializeEx( + [In, Optional] IntPtr pvReserved, + [In] COINIT dwCoInit //DWORD + ); + + /// If function succeeds, it returns 0(S_OK). Otherwise, it returns an error code. + [DllImport("ole32.dll", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)] + private static extern int CoUnInitialize(); + + [DllImport("ole32.dll")] + private static extern int CoInitializeSecurity( + IntPtr pSecDesc, + int cAuthSvc, + SOLE_AUTHENTICATION_SERVICE[] asAuthSvc, + IntPtr pReserved1, + uint dwAuthnLevel, + uint dwImpLevel, + IntPtr pAuthList, + uint dwCapabilities, + IntPtr pReserved3); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COSERVERINFO + { + public uint dwReserved1; + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszName; + public IntPtr pAuthInfo; + public uint dwReserved2; + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COAUTHINFO + { + public uint dwAuthnSvc; + public uint dwAuthzSvc; + public IntPtr pwszServerPrincName; + public uint dwAuthnLevel; + public uint dwImpersonationLevel; + public IntPtr pAuthIdentityData; + public uint dwCapabilities; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COAUTHIDENTITY + { + public IntPtr User; + public uint UserLength; + public IntPtr Domain; + public uint DomainLength; + public IntPtr Password; + public uint PasswordLength; + public uint Flags; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MULTI_QI + { + public IntPtr iid; + [MarshalAs(UnmanagedType.IUnknown)] + public object pItf; + public uint hr; + } + + private const uint CLSCTX_INPROC_SERVER = 0x1; + private const uint CLSCTX_INPROC_HANDLER = 0x2; + private const uint CLSCTX_LOCAL_SERVER = 0x4; + private const uint CLSCTX_REMOTE_SERVER = 0x10; + + private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); + + private const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1; + private const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2; + + [DllImport("ole32.dll")] + private static extern void CoCreateInstanceEx( + ref Guid clsid, + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + uint dwClsCtx, + [In] + ref COSERVERINFO pServerInfo, + uint dwCount, + [In, Out] + MULTI_QI[] pResults); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct LICINFO + { + public int cbLicInfo; + [MarshalAs(UnmanagedType.Bool)] + public bool fRuntimeKeyAvail; + [MarshalAs(UnmanagedType.Bool)] + public bool fLicVerified; + } + + [ComImport] + [GuidAttribute("B196B28F-BAB4-101A-B69C-00AA00341D07")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClassFactory2 + { + void CreateInstance( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.Interface)] + [Out] out object ppvObject); + + void LockServer( + [MarshalAs(UnmanagedType.Bool)] + bool fLock); + + void GetLicInfo( + [In, Out] ref LICINFO pLicInfo); + + void RequestLicKey( + int dwReserved, + [MarshalAs(UnmanagedType.BStr)] + string pbstrKey); + + void CreateInstanceLic( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.IUnknown)] + object punkReserved, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.BStr)] + string bstrKey, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppvObject); + } + + [ComImport] + [GuidAttribute("0000013D-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClientSecurity + { + void QueryBlanket( + [MarshalAs(UnmanagedType.IUnknown)] + object pProxy, + ref uint pAuthnSvc, + ref uint pAuthzSvc, + [MarshalAs(UnmanagedType.LPWStr)] + ref string pServerPrincName, + ref uint pAuthnLevel, + ref uint pImpLevel, + ref IntPtr pAuthInfo, + ref uint pCapabilities); + + void SetBlanket( + [MarshalAs(UnmanagedType.IUnknown)] + object pProxy, + uint pAuthnSvc, + uint pAuthzSvc, + [MarshalAs(UnmanagedType.LPWStr)] + string pServerPrincName, + uint pAuthnLevel, + uint pImpLevel, + IntPtr pAuthInfo, + uint pCapabilities); + + void CopyProxy( + [MarshalAs(UnmanagedType.IUnknown)] + object pProxy, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppCopy); + } + + [DllImport("ole32.dll")] + private static extern void CoGetClassObject( + [MarshalAs(UnmanagedType.LPStruct)] + Guid clsid, + uint dwClsContext, + [In] ref COSERVERINFO pServerInfo, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppv); + #endregion + + #region ServerInfo Class + /// + /// A class used to allocate and deallocate the elements of a COSERVERINFO structure. + /// + class ServerInfo + { + #region Public Interface + /// + /// Allocates a COSERVERINFO structure. + /// + public COSERVERINFO Allocate(string hostName, NetworkCredential credential, bool useConnectSecurity = false) + { + string userName = null; + string password = null; + string domain = null; + + if (credential != null) + { + userName = credential.UserName; + password = credential.Password; + domain = credential.Domain; + } + + m_hUserName = GCHandle.Alloc(userName, GCHandleType.Pinned); + m_hPassword = GCHandle.Alloc(password, GCHandleType.Pinned); + m_hDomain = GCHandle.Alloc(domain, GCHandleType.Pinned); + + m_hIdentity = new GCHandle(); + + if (userName != null && userName != string.Empty) + { + var identity = new COAUTHIDENTITY(); + + identity.User = m_hUserName.AddrOfPinnedObject(); + identity.UserLength = (uint)((userName != null) ? userName.Length : 0); + identity.Password = m_hPassword.AddrOfPinnedObject(); + identity.PasswordLength = (uint)((password != null) ? password.Length : 0); + identity.Domain = m_hDomain.AddrOfPinnedObject(); + identity.DomainLength = (uint)((domain != null) ? domain.Length : 0); + identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + m_hIdentity = GCHandle.Alloc(identity, GCHandleType.Pinned); + } + + var authInfo = new COAUTHINFO(); + authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; + authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; + authInfo.pwszServerPrincName = IntPtr.Zero; + authInfo.dwAuthnLevel = (useConnectSecurity) ? RPC_C_AUTHN_LEVEL_CONNECT : RPC_C_AUTHN_LEVEL_PKT_INTEGRITY; + authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + authInfo.pAuthIdentityData = (m_hIdentity.IsAllocated) ? m_hIdentity.AddrOfPinnedObject() : IntPtr.Zero; + authInfo.dwCapabilities = EOAC_NONE; + + m_hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned); + + var serverInfo = new COSERVERINFO(); + + serverInfo.pwszName = hostName; + serverInfo.pAuthInfo = (credential != null) ? m_hAuthInfo.AddrOfPinnedObject() : IntPtr.Zero; + serverInfo.dwReserved1 = 0; + serverInfo.dwReserved2 = 0; + + return serverInfo; + } + + /// + /// Deallocated memory allocated when the COSERVERINFO structure was created. + /// + public void Deallocate() + { + if (m_hUserName.IsAllocated) m_hUserName.Free(); + if (m_hPassword.IsAllocated) m_hPassword.Free(); + if (m_hDomain.IsAllocated) m_hDomain.Free(); + if (m_hIdentity.IsAllocated) m_hIdentity.Free(); + if (m_hAuthInfo.IsAllocated) m_hAuthInfo.Free(); + } + #endregion + + #region Private Members + GCHandle m_hUserName; + GCHandle m_hPassword; + GCHandle m_hDomain; + GCHandle m_hIdentity; + GCHandle m_hAuthInfo; + #endregion + } + #endregion + + /// + /// Initializes the COM library for use by the calling thread, sets the thread's concurrency model, and creates a new apartment for the thread if one is required. Before thread exit UnInitialize() must be called. + /// + /// The chosen threading model. Must be used for all threads. + public static void Initialize(uint coInit) + { + var error = CoInitializeEx(IntPtr.Zero, (COINIT)coInit); + + var result = GetResultID(error); + + if (result.IsError()) + { + throw new ExternalException("CoInitializeEx: " + GetSystemMessage(error), error); + } + } + + /// + /// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other resources that the thread maintains, and forces all RPC connections on the thread to close. + /// + public static void UnInitialize() + { + var error = CoUnInitialize(); + + var result = GetResultID(error); + + if (result.IsError()) + { + throw new ExternalException("CoUnInitialize: " + GetSystemMessage(error), error); + } + } + + /// + /// Initializes COM security. + /// + /// The default authentication level for the process. Both servers and clients use this parameter when they call CoInitializeSecurity. With the Windows Update KB5004442 a higher authentication level of Integrity must be used. + public static void InitializeSecurity(uint authenticationLevel) + { + var error = CoInitializeSecurity( + IntPtr.Zero, + -1, + null, + IntPtr.Zero, + authenticationLevel, + RPC_C_IMP_LEVEL_IDENTIFY, + IntPtr.Zero, + EOAC_NONE, + IntPtr.Zero); + + var result = GetResultID(error); + + if (result.IsError()) + { + throw new ExternalException("CoInitializeSecurity: " + GetSystemMessage(error), error); + } + } + + /// + /// Creates an instance of a COM server. + /// + public static object CreateInstance(Guid clsid, string hostName, NetworkCredential credential, bool useConnectSecurity = false) + { + var serverInfo = new ServerInfo(); + var coserverInfo = serverInfo.Allocate(hostName, credential, useConnectSecurity); + + var hIID = GCHandle.Alloc(IID_IUnknown, GCHandleType.Pinned); + + var results = new MULTI_QI[1]; + + results[0].iid = hIID.AddrOfPinnedObject(); + results[0].pItf = null; + results[0].hr = 0; + + try + { + // check whether connecting locally or remotely. + var clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; + + if (hostName != null && hostName.Length > 0 && hostName != "localhost") + { + clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + } + + // create an instance. + CoCreateInstanceEx( + ref clsid, + null, + clsctx, + ref coserverInfo, + 1, + results); + } + finally + { + if (hIID.IsAllocated) hIID.Free(); + + serverInfo.Deallocate(); + } + + if (results[0].hr != 0) + { + throw new ExternalException("CoCreateInstanceEx: " + GetSystemMessage((int)results[0].hr)); + } + + return results[0].pItf; + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstanceWithLicenseKey(Guid clsid, string hostName, NetworkCredential credential, string licenseKey) + { + var serverInfo = new ServerInfo(); + var coserverInfo = serverInfo.Allocate(hostName, credential); + object instance = null; + try + { + // check whether connecting locally or remotely. + var clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; + + if (hostName != null && hostName.Length > 0) + { + clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + } + + DCOMCallWatchdog.Set(); + + // get the class factory. + object unknown = null; + + CoGetClassObject( + clsid, + clsctx, + ref coserverInfo, + typeof(IClassFactory2).GUID, + out unknown); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new ExternalException("CoGetClassObject: COM Call was cancelled"); + } + + var factory = (IClassFactory2)unknown; + + // set the proper connect authentication level + var security = (IClientSecurity)factory; + + uint pAuthnSvc = 0; + uint pAuthzSvc = 0; + var pServerPrincName = ""; + uint pAuthnLevel = 0; + uint pImpLevel = 0; + var pAuthInfo = IntPtr.Zero; + uint pCapabilities = 0; + + // get existing security settings. + security.QueryBlanket( + factory, + ref pAuthnSvc, + ref pAuthzSvc, + ref pServerPrincName, + ref pAuthnLevel, + ref pImpLevel, + ref pAuthInfo, + ref pCapabilities); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new ExternalException("CoGetClassObject: COM Call was cancelled"); + } + + pAuthnSvc = RPC_C_AUTHN_DEFAULT; + pAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; + + // update security settings. + security.SetBlanket( + factory, + pAuthnSvc, + pAuthzSvc, + pServerPrincName, + pAuthnLevel, + pImpLevel, + pAuthInfo, + pCapabilities); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new ExternalException("CoGetClassObject: COM Call was cancelled"); + } + + // create instance. + factory.CreateInstanceLic( + null, + null, + IID_IUnknown, + licenseKey, + out instance); + + if (DCOMCallWatchdog.IsCancelled) + { + throw new ExternalException("CoGetClassObject: COM Call was cancelled"); + } + + DCOMCallWatchdog.Reset(); + + } + finally + { + serverInfo.Deallocate(); + } + + return instance; + } + + /// + /// Unmarshals and frees an array of 32 bit integers. + /// + public static int[] GetInt32s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new int[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Allocates and marshals an array of 32 bit integers. + /// + public static IntPtr GetInt32s(int[] input) + { + var output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Unmarshals and frees a array of 16 bit integers. + /// + public static short[] GetInt16s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new short[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Allocates and marshals an array of 16 bit integers. + /// + public static IntPtr GetInt16s(short[] input) + { + var output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(short)) * input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Marshals an array of strings into a unmanaged memory buffer + /// + /// The array of strings to marshal + /// The pointer to the unmanaged memory buffer + public static IntPtr GetUnicodeStrings(string[] values) + { + var size = (values != null) ? values.Length : 0; + + if (size <= 0) + { + return IntPtr.Zero; + } + + var pointers = new IntPtr[size]; + + for (var ii = 0; ii < size; ii++) + { + pointers[ii] = Marshal.StringToCoTaskMemUni(values[ii]); + } + + var pValues = Marshal.AllocCoTaskMem(values.Length * Marshal.SizeOf(typeof(IntPtr))); + Marshal.Copy(pointers, 0, pValues, size); + + return pValues; + } + + /// + /// Unmarshals and frees a array of unicode strings. + /// + public static string[] GetUnicodeStrings(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var pointers = new IntPtr[size]; + Marshal.Copy(pArray, pointers, 0, size); + + var strings = new string[size]; + + for (var ii = 0; ii < size; ii++) + { + var pString = pointers[ii]; + strings[ii] = Marshal.PtrToStringUni(pString); + if (deallocate) Marshal.FreeCoTaskMem(pString); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return strings; + } + + /// + /// This flag suppresses the conversion to local time done during marshalling. + /// + public static bool PreserveUtc { + get { lock (typeof(Interop)) { return DaAeHdaClient.ApplicationInstance.TimeAsUtc; } } + set { lock (typeof(Interop)) { DaAeHdaClient.ApplicationInstance.TimeAsUtc = value; } } + } + + private static readonly DateTime FILETIME_BaseTime = new DateTime(1601, 1, 1); + + /// + /// Marshals a DateTime as a WIN32 FILETIME. + /// + /// The DateTime object to marshal + /// The WIN32 FILETIME + public static FILETIME GetFILETIME(DateTime datetime) + { + FILETIME filetime; + + if (datetime <= FILETIME_BaseTime) + { + filetime.dwHighDateTime = 0; + filetime.dwLowDateTime = 0; + return filetime; + } + + // adjust for WIN32 FILETIME base. + long ticks; + if (PreserveUtc) + { + ticks = datetime.Subtract(new TimeSpan(FILETIME_BaseTime.Ticks)).Ticks; + } + else + { + ticks = (datetime.ToUniversalTime().Subtract(new TimeSpan(FILETIME_BaseTime.Ticks))).Ticks; + } + + filetime.dwHighDateTime = (int)((ticks >> 32) & 0xFFFFFFFF); + filetime.dwLowDateTime = (int)(ticks & 0xFFFFFFFF); + + return filetime; + } + + /// + /// Unmarshals a WIN32 FILETIME from a pointer. + /// + /// A pointer to a FILETIME structure. + /// A DateTime object. + public static DateTime GetFILETIME(IntPtr pFiletime) + { + if (pFiletime == IntPtr.Zero) + { + return DateTime.MinValue; + } + + return GetFILETIME((FILETIME)Marshal.PtrToStructure(pFiletime, typeof(FILETIME))); + } + + /// + /// Unmarshals a WIN32 FILETIME. + /// + public static DateTime GetFILETIME(FILETIME filetime) + { + // convert FILETIME structure to a 64 bit integer. + var buffer = (long)filetime.dwHighDateTime; + + if (buffer < 0) + { + buffer += ((long)uint.MaxValue + 1); + } + + var ticks = (buffer << 32); + + buffer = (long)filetime.dwLowDateTime; + + if (buffer < 0) + { + buffer += ((long)uint.MaxValue + 1); + } + + ticks += buffer; + + // check for invalid value. + if (ticks == 0) + { + return DateTime.MinValue; + } + + // adjust for WIN32 FILETIME base. + if (PreserveUtc) + { + return FILETIME_BaseTime.Add(new TimeSpan(ticks)); + } + else + { + return FILETIME_BaseTime.Add(new TimeSpan(ticks)).ToLocalTime(); + } + } + + /// + /// Marshals an array of DateTimes into an unmanaged array of FILETIMEs + /// + /// The array of DateTimes to marshal + /// The IntPtr array of FILETIMEs + public static IntPtr GetFILETIMEs(DateTime[] datetimes) + { + var count = (datetimes != null) ? datetimes.Length : 0; + + if (count <= 0) + { + return IntPtr.Zero; + } + + var pFiletimes = Marshal.AllocCoTaskMem(count * Marshal.SizeOf(typeof(FILETIME))); + + var pos = pFiletimes; + + for (var ii = 0; ii < count; ii++) + { + Marshal.StructureToPtr(GetFILETIME(datetimes[ii]), pos, false); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(FILETIME))); + } + + return pFiletimes; + } + + /// + /// Unmarshals an array of WIN32 FILETIMEs as DateTimes. + /// + public static DateTime[] GetFILETIMEs(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var datetimes = new DateTime[size]; + + var pos = pArray; + + for (var ii = 0; ii < size; ii++) + { + datetimes[ii] = GetFILETIME(pos); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(FILETIME))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return datetimes; + } + + /// + /// WIN32 GUID struct declaration. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct GUID + { + public int Data1; + public short Data2; + public short Data3; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Data4; + } + + /// + /// Unmarshals an array of WIN32 GUIDs as Guid. + /// + public static Guid[] GetGUIDs(ref IntPtr pInput, int size, bool deallocate) + { + if (pInput == IntPtr.Zero || size <= 0) + { + return null; + } + + var guids = new Guid[size]; + + var pos = pInput; + + for (var ii = 0; ii < size; ii++) + { + var input = (GUID)Marshal.PtrToStructure(pInput, typeof(GUID)); + + guids[ii] = new Guid(input.Data1, input.Data2, input.Data3, input.Data4); + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(GUID))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + + return guids; + } + + /// + /// The size, in bytes, of a VARIANT structure. + /// + private static int VARIANT_SIZE => (IntPtr.Size > 4) ? 0x18 : 0x10; + + /// + /// Frees all memory referenced by a VARIANT stored in unmanaged memory. + /// + [DllImport("oleaut32.dll")] + public static extern void VariantClear(IntPtr pVariant); + + /// + /// Converts an object into a value that can be marshalled to a VARIANT. + /// + /// The object to convert. + /// The converted object. + public static object GetVARIANT(object source) + { + // check for invalid args. + if (source == null || source.GetType() == null) + { + return null; + } + + // convert a decimal array to an object array since decimal arrays can't be converted to a variant. + if (source.GetType() == typeof(decimal[])) + { + var srcArray = (decimal[])source; + var dstArray = new object[srcArray.Length]; + + for (var ii = 0; ii < srcArray.Length; ii++) + { + try + { + dstArray[ii] = (object)srcArray[ii]; + } + catch (Exception) + { + dstArray[ii] = double.NaN; + } + } + + return dstArray; + } + + // no conversion required. + return source; + } + + /// + /// Marshals an array objects into an unmanaged array of VARIANTs. + /// + /// An array of the objects to be marshalled + /// Whether the objects should have troublesome types removed before marhalling. + /// An pointer to the array in unmanaged memory + public static IntPtr GetVARIANTs(object[] values, bool preprocess) + { + var count = (values != null) ? values.Length : 0; + + if (count <= 0) + { + return IntPtr.Zero; + } + + var pValues = Marshal.AllocCoTaskMem(count * VARIANT_SIZE); + + var pos = pValues; + + for (var ii = 0; ii < count; ii++) + { + if (preprocess) + { + Marshal.GetNativeVariantForObject(GetVARIANT(values[ii]), pos); + } + else + { + Marshal.GetNativeVariantForObject(values[ii], pos); + } + + pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE); + } + + return pValues; + } + + /// + /// Unmarshals an array of VARIANTs as objects. + /// + public static object[] GetVARIANTs(ref IntPtr pArray, int size, bool deallocate) + { + // this method unmarshals VARIANTs one at a time because a single bad value throws + // an exception with GetObjectsForNativeVariants(). This approach simply sets the + // offending value to null. + + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var values = new object[size]; + + var pos = pArray; + + var bytes = new byte[size * VARIANT_SIZE]; + Marshal.Copy(pos, bytes, 0, bytes.Length); + + for (var ii = 0; ii < size; ii++) + { + try + { + values[ii] = Marshal.GetObjectForNativeVariant(pos); + if (deallocate) VariantClear(pos); + } + catch (Exception) + { + values[ii] = null; + } + + pos = (IntPtr)(pos.ToInt64() + VARIANT_SIZE); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return values; + } + + /// + /// The constant used to selected the default locale. + /// + internal const int LOCALE_SYSTEM_DEFAULT = 0x800; + internal const int LOCALE_USER_DEFAULT = 0x400; + + /// + /// Converts a LCID to a Locale string. + /// + internal static string GetLocale(int input) + { + try + { + if (input == LOCALE_SYSTEM_DEFAULT || input == LOCALE_USER_DEFAULT || input == 0) + { + return CultureInfo.InvariantCulture.Name; + } + + return new CultureInfo(input).Name; + } + catch + { + throw new ExternalException("Invalid LCID", OpcResult.E_INVALIDARG.Code); + } + } + + /// + /// Converts a Locale string to a LCID. + /// + internal static int GetLocale(string input) + { + // check for the default culture. + if (input == null || input == "") + { + return 0; + } + + CultureInfo locale; + try { locale = new CultureInfo(input); } + catch { locale = CultureInfo.CurrentCulture; } + + return locale.LCID; + } + + /// + /// Converts the VARTYPE to a system type. + /// + internal static Type GetType(VarEnum input) + { + switch (input) + { + case VarEnum.VT_EMPTY: return null; + case VarEnum.VT_I1: return typeof(sbyte); + case VarEnum.VT_UI1: return typeof(byte); + case VarEnum.VT_I2: return typeof(short); + case VarEnum.VT_UI2: return typeof(ushort); + case VarEnum.VT_I4: return typeof(int); + case VarEnum.VT_UI4: return typeof(uint); + case VarEnum.VT_I8: return typeof(long); + case VarEnum.VT_UI8: return typeof(ulong); + case VarEnum.VT_R4: return typeof(float); + case VarEnum.VT_R8: return typeof(double); + case VarEnum.VT_CY: return typeof(decimal); + case VarEnum.VT_BOOL: return typeof(bool); + case VarEnum.VT_DATE: return typeof(DateTime); + case VarEnum.VT_BSTR: return typeof(string); + case VarEnum.VT_ARRAY | VarEnum.VT_I1: return typeof(sbyte[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI1: return typeof(byte[]); + case VarEnum.VT_ARRAY | VarEnum.VT_I2: return typeof(short[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI2: return typeof(ushort[]); + case VarEnum.VT_ARRAY | VarEnum.VT_I4: return typeof(int[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI4: return typeof(uint[]); + case VarEnum.VT_ARRAY | VarEnum.VT_I8: return typeof(long[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI8: return typeof(ulong[]); + case VarEnum.VT_ARRAY | VarEnum.VT_R4: return typeof(float[]); + case VarEnum.VT_ARRAY | VarEnum.VT_R8: return typeof(double[]); + case VarEnum.VT_ARRAY | VarEnum.VT_CY: return typeof(decimal[]); + case VarEnum.VT_ARRAY | VarEnum.VT_BOOL: return typeof(bool[]); + case VarEnum.VT_ARRAY | VarEnum.VT_DATE: return typeof(DateTime[]); + case VarEnum.VT_ARRAY | VarEnum.VT_BSTR: return typeof(string[]); + case VarEnum.VT_ARRAY | VarEnum.VT_VARIANT: return typeof(object[]); + default: return OpcType.ILLEGAL_TYPE; + } + } + + /// + /// Converts the system type to a VARTYPE. + /// + internal static VarEnum GetType(Type input) + { + if (input == null) return VarEnum.VT_EMPTY; + if (input == typeof(sbyte)) return VarEnum.VT_I1; + if (input == typeof(byte)) return VarEnum.VT_UI1; + if (input == typeof(short)) return VarEnum.VT_I2; + if (input == typeof(ushort)) return VarEnum.VT_UI2; + if (input == typeof(int)) return VarEnum.VT_I4; + if (input == typeof(uint)) return VarEnum.VT_UI4; + if (input == typeof(long)) return VarEnum.VT_I8; + if (input == typeof(ulong)) return VarEnum.VT_UI8; + if (input == typeof(float)) return VarEnum.VT_R4; + if (input == typeof(double)) return VarEnum.VT_R8; + if (input == typeof(decimal)) return VarEnum.VT_CY; + if (input == typeof(bool)) return VarEnum.VT_BOOL; + if (input == typeof(DateTime)) return VarEnum.VT_DATE; + if (input == typeof(string)) return VarEnum.VT_BSTR; + if (input == typeof(object)) return VarEnum.VT_EMPTY; + if (input == typeof(sbyte[])) return VarEnum.VT_ARRAY | VarEnum.VT_I1; + if (input == typeof(byte[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI1; + if (input == typeof(short[])) return VarEnum.VT_ARRAY | VarEnum.VT_I2; + if (input == typeof(ushort[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI2; + if (input == typeof(int[])) return VarEnum.VT_ARRAY | VarEnum.VT_I4; + if (input == typeof(uint[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI4; + if (input == typeof(long[])) return VarEnum.VT_ARRAY | VarEnum.VT_I8; + if (input == typeof(ulong[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI8; + if (input == typeof(float[])) return VarEnum.VT_ARRAY | VarEnum.VT_R4; + if (input == typeof(double[])) return VarEnum.VT_ARRAY | VarEnum.VT_R8; + if (input == typeof(decimal[])) return VarEnum.VT_ARRAY | VarEnum.VT_CY; + if (input == typeof(bool[])) return VarEnum.VT_ARRAY | VarEnum.VT_BOOL; + if (input == typeof(DateTime[])) return VarEnum.VT_ARRAY | VarEnum.VT_DATE; + if (input == typeof(string[])) return VarEnum.VT_ARRAY | VarEnum.VT_BSTR; + if (input == typeof(object[])) return VarEnum.VT_ARRAY | VarEnum.VT_VARIANT; + + // check for special types. + if (input == OpcType.ILLEGAL_TYPE) return (VarEnum)Enum.ToObject(typeof(VarEnum), 0x7FFF); + if (input == typeof(Type)) return VarEnum.VT_I2; + if (input == typeof(TsCDaQuality)) return VarEnum.VT_I2; + if (input == typeof(TsDaAccessRights)) return VarEnum.VT_I4; + if (input == typeof(TsDaEuType)) return VarEnum.VT_I4; + return VarEnum.VT_EMPTY; + } + + /// + /// Converts the HRESULT to a system type. + /// + internal static OpcResult GetResultID(int input) + { + switch (input) + { + // data access. + case Da.Result.S_OK: return new OpcResult(OpcResult.S_OK, input); + case Da.Result.E_FAIL: return new OpcResult(OpcResult.E_FAIL, input); + case Da.Result.E_INVALIDARG: return new OpcResult(OpcResult.E_INVALIDARG, input); + case Da.Result.DISP_E_TYPEMISMATCH: return new OpcResult(OpcResult.Da.E_BADTYPE, input); + case Da.Result.DISP_E_OVERFLOW: return new OpcResult(OpcResult.Da.E_RANGE, input); + case Da.Result.E_OUTOFMEMORY: return new OpcResult(OpcResult.E_OUTOFMEMORY, input); + case Da.Result.E_NOINTERFACE: return new OpcResult(OpcResult.E_NOTSUPPORTED, input); + case Da.Result.E_INVALIDHANDLE: return new OpcResult(OpcResult.Da.E_INVALIDHANDLE, input); + case Da.Result.E_BADTYPE: return new OpcResult(OpcResult.Da.E_BADTYPE, input); + case Da.Result.E_UNKNOWNITEMID: return new OpcResult(OpcResult.Da.E_UNKNOWN_ITEM_NAME, input); + case Da.Result.E_INVALIDITEMID: return new OpcResult(OpcResult.Da.E_INVALID_ITEM_NAME, input); + case Da.Result.E_UNKNOWNPATH: return new OpcResult(OpcResult.Da.E_UNKNOWN_ITEM_PATH, input); + case Da.Result.E_INVALIDFILTER: return new OpcResult(OpcResult.Da.E_INVALID_FILTER, input); + case Da.Result.E_RANGE: return new OpcResult(OpcResult.Da.E_RANGE, input); + case Da.Result.E_DUPLICATENAME: return new OpcResult(OpcResult.Da.E_DUPLICATENAME, input); + case Da.Result.S_UNSUPPORTEDRATE: return new OpcResult(OpcResult.Da.S_UNSUPPORTEDRATE, input); + case Da.Result.S_CLAMP: return new OpcResult(OpcResult.Da.S_CLAMP, input); + case Da.Result.E_INVALID_PID: return new OpcResult(OpcResult.Da.E_INVALID_PID, input); + case Da.Result.E_DEADBANDNOTSUPPORTED: return new OpcResult(OpcResult.Da.E_NO_ITEM_DEADBAND, input); + case Da.Result.E_NOBUFFERING: return new OpcResult(OpcResult.Da.E_NO_ITEM_BUFFERING, input); + case Da.Result.E_NOTSUPPORTED: return new OpcResult(OpcResult.Da.E_NO_WRITEQT, input); + case Da.Result.E_INVALIDCONTINUATIONPOINT: return new OpcResult(OpcResult.Da.E_INVALIDCONTINUATIONPOINT, input); + case Da.Result.S_DATAQUEUEOVERFLOW: return new OpcResult(OpcResult.Da.S_DATAQUEUEOVERFLOW, input); + + // complex data. + case Cpx.Result.E_TYPE_CHANGED: return new OpcResult(OpcResult.Cpx.E_TYPE_CHANGED, input); + case Cpx.Result.E_FILTER_DUPLICATE: return new OpcResult(OpcResult.Cpx.E_FILTER_DUPLICATE, input); + case Cpx.Result.E_FILTER_INVALID: return new OpcResult(OpcResult.Cpx.E_FILTER_INVALID, input); + case Cpx.Result.E_FILTER_ERROR: return new OpcResult(OpcResult.Cpx.E_FILTER_ERROR, input); + case Cpx.Result.S_FILTER_NO_DATA: return new OpcResult(OpcResult.Cpx.S_FILTER_NO_DATA, input); + + // historical data access. + case Hda.Result.E_MAXEXCEEDED: return new OpcResult(OpcResult.Hda.E_MAXEXCEEDED, input); + case Hda.Result.S_NODATA: return new OpcResult(OpcResult.Hda.S_NODATA, input); + case Hda.Result.S_MOREDATA: return new OpcResult(OpcResult.Hda.S_MOREDATA, input); + case Hda.Result.E_INVALIDAGGREGATE: return new OpcResult(OpcResult.Hda.E_INVALIDAGGREGATE, input); + case Hda.Result.S_CURRENTVALUE: return new OpcResult(OpcResult.Hda.S_CURRENTVALUE, input); + case Hda.Result.S_EXTRADATA: return new OpcResult(OpcResult.Hda.S_EXTRADATA, input); + case Hda.Result.W_NOFILTER: return new OpcResult(OpcResult.Hda.W_NOFILTER, input); + case Hda.Result.E_UNKNOWNATTRID: return new OpcResult(OpcResult.Hda.E_UNKNOWNATTRID, input); + case Hda.Result.E_NOT_AVAIL: return new OpcResult(OpcResult.Hda.E_NOT_AVAIL, input); + case Hda.Result.E_INVALIDDATATYPE: return new OpcResult(OpcResult.Hda.E_INVALIDDATATYPE, input); + case Hda.Result.E_DATAEXISTS: return new OpcResult(OpcResult.Hda.E_DATAEXISTS, input); + case Hda.Result.E_INVALIDATTRID: return new OpcResult(OpcResult.Hda.E_INVALIDATTRID, input); + case Hda.Result.E_NODATAEXISTS: return new OpcResult(OpcResult.Hda.E_NODATAEXISTS, input); + case Hda.Result.S_INSERTED: return new OpcResult(OpcResult.Hda.S_INSERTED, input); + case Hda.Result.S_REPLACED: return new OpcResult(OpcResult.Hda.S_REPLACED, input); + + // Alarms and Events. + case Ae.Result.S_ALREADYACKED: return new OpcResult(OpcResult.Ae.S_ALREADYACKED, input); + case Ae.Result.S_INVALIDBUFFERTIME: return new OpcResult(OpcResult.Ae.S_INVALIDBUFFERTIME, input); + case Ae.Result.S_INVALIDMAXSIZE: return new OpcResult(OpcResult.Ae.S_INVALIDMAXSIZE, input); + case Ae.Result.S_INVALIDKEEPALIVETIME: return new OpcResult(OpcResult.Ae.S_INVALIDKEEPALIVETIME, input); + + // This function returns Da.Result.E_INVALID_PID. AE specific code must map to E_INVALIDBRANCHNAME. + // case Technosoftware.DaAeHdaClient.Com.Ae.Result.E_INVALIDBRANCHNAME: return new OpcResult(OpcResult.Ae.E_INVALIDBRANCHNAME, input); + + case Ae.Result.E_INVALIDTIME: return new OpcResult(OpcResult.Ae.E_INVALIDTIME, input); + case Ae.Result.E_BUSY: return new OpcResult(OpcResult.Ae.E_BUSY, input); + case Ae.Result.E_NOINFO: return new OpcResult(OpcResult.Ae.E_NOINFO, input); + + default: + { + // check for RPC error. + if ((input & 0x7FFF0000) == 0x00010000) + { + return new OpcResult(OpcResult.E_NETWORK_ERROR, input); + } + + // chekc for success code. + if (input >= 0) + { + return new OpcResult(OpcResult.S_FALSE, input); + } + + // return generic error. + return new OpcResult(OpcResult.E_FAIL, input); + } + } + } + + /// + /// Converts a result id to an HRESULT. + /// + internal static int GetResultID(OpcResult input) + { + // data access. + if (input.Name != null && input.Name.Namespace == OpcNamespace.OPC_DATA_ACCESS) + { + if (input == OpcResult.S_OK) return Da.Result.S_OK; + if (input == OpcResult.E_FAIL) return Da.Result.E_FAIL; + if (input == OpcResult.E_INVALIDARG) return Da.Result.E_INVALIDARG; + if (input == OpcResult.Da.E_BADTYPE) return Da.Result.E_BADTYPE; + if (input == OpcResult.Da.E_READONLY) return Da.Result.E_BADRIGHTS; + if (input == OpcResult.Da.E_WRITEONLY) return Da.Result.E_BADRIGHTS; + if (input == OpcResult.Da.E_RANGE) return Da.Result.E_RANGE; + if (input == OpcResult.E_OUTOFMEMORY) return Da.Result.E_OUTOFMEMORY; + if (input == OpcResult.E_NOTSUPPORTED) return Da.Result.E_NOINTERFACE; + if (input == OpcResult.Da.E_INVALIDHANDLE) return Da.Result.E_INVALIDHANDLE; + if (input == OpcResult.Da.E_UNKNOWN_ITEM_NAME) return Da.Result.E_UNKNOWNITEMID; + if (input == OpcResult.Da.E_INVALID_ITEM_NAME) return Da.Result.E_INVALIDITEMID; + if (input == OpcResult.Da.E_INVALID_ITEM_PATH) return Da.Result.E_INVALIDITEMID; + if (input == OpcResult.Da.E_UNKNOWN_ITEM_PATH) return Da.Result.E_UNKNOWNPATH; + if (input == OpcResult.Da.E_INVALID_FILTER) return Da.Result.E_INVALIDFILTER; + if (input == OpcResult.Da.S_UNSUPPORTEDRATE) return Da.Result.S_UNSUPPORTEDRATE; + if (input == OpcResult.Da.S_CLAMP) return Da.Result.S_CLAMP; + if (input == OpcResult.Da.E_INVALID_PID) return Da.Result.E_INVALID_PID; + if (input == OpcResult.Da.E_NO_ITEM_DEADBAND) return Da.Result.E_DEADBANDNOTSUPPORTED; + if (input == OpcResult.Da.E_NO_ITEM_BUFFERING) return Da.Result.E_NOBUFFERING; + if (input == OpcResult.Da.E_NO_WRITEQT) return Da.Result.E_NOTSUPPORTED; + if (input == OpcResult.Da.E_INVALIDCONTINUATIONPOINT) return Da.Result.E_INVALIDCONTINUATIONPOINT; + if (input == OpcResult.Da.S_DATAQUEUEOVERFLOW) return Da.Result.S_DATAQUEUEOVERFLOW; + } + + // complex data. + else if (input.Name != null && input.Name.Namespace == OpcNamespace.OPC_COMPLEX_DATA) + { + if (input == OpcResult.Cpx.E_TYPE_CHANGED) return Cpx.Result.E_TYPE_CHANGED; + if (input == OpcResult.Cpx.E_FILTER_DUPLICATE) return Cpx.Result.E_FILTER_DUPLICATE; + if (input == OpcResult.Cpx.E_FILTER_INVALID) return Cpx.Result.E_FILTER_INVALID; + if (input == OpcResult.Cpx.E_FILTER_ERROR) return Cpx.Result.E_FILTER_ERROR; + if (input == OpcResult.Cpx.S_FILTER_NO_DATA) return Cpx.Result.S_FILTER_NO_DATA; + } + + // historical data access. + else if (input.Name != null && input.Name.Namespace == OpcNamespace.OPC_HISTORICAL_DATA_ACCESS) + { + if (input == OpcResult.Hda.E_MAXEXCEEDED) return Hda.Result.E_MAXEXCEEDED; + if (input == OpcResult.Hda.S_NODATA) return Hda.Result.S_NODATA; + if (input == OpcResult.Hda.S_MOREDATA) return Hda.Result.S_MOREDATA; + if (input == OpcResult.Hda.E_INVALIDAGGREGATE) return Hda.Result.E_INVALIDAGGREGATE; + if (input == OpcResult.Hda.S_CURRENTVALUE) return Hda.Result.S_CURRENTVALUE; + if (input == OpcResult.Hda.S_EXTRADATA) return Hda.Result.S_EXTRADATA; + if (input == OpcResult.Hda.E_UNKNOWNATTRID) return Hda.Result.E_UNKNOWNATTRID; + if (input == OpcResult.Hda.E_NOT_AVAIL) return Hda.Result.E_NOT_AVAIL; + if (input == OpcResult.Hda.E_INVALIDDATATYPE) return Hda.Result.E_INVALIDDATATYPE; + if (input == OpcResult.Hda.E_DATAEXISTS) return Hda.Result.E_DATAEXISTS; + if (input == OpcResult.Hda.E_INVALIDATTRID) return Hda.Result.E_INVALIDATTRID; + if (input == OpcResult.Hda.E_NODATAEXISTS) return Hda.Result.E_NODATAEXISTS; + if (input == OpcResult.Hda.S_INSERTED) return Hda.Result.S_INSERTED; + if (input == OpcResult.Hda.S_REPLACED) return Hda.Result.S_REPLACED; + } + + // check for custom code. + else if (input.Code == -1) + { + // default success code. + if (input.Succeeded()) + { + return Da.Result.S_FALSE; + } + + // default error code. + return Da.Result.E_FAIL; + } + + // return custom code. + return input.Code; + } + + /// + /// Returns an exception after extracting HRESULT from the exception. + /// + public static Exception CreateException(string message, Exception e) + { + return CreateException(message, Marshal.GetHRForException(e)); + } + + /// + /// Returns an exception after extracting HRESULT from the exception. + /// + public static Exception CreateException(string message, int code) + { + return new OpcResultException(GetResultID(code), message); + } + + /// + /// Releases the server if it is a true COM server. + /// + public static void ReleaseServer(object server) + { + if (server != null && server.GetType().IsCOMObject) + { + Marshal.ReleaseComObject(server); + } + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/OpcDiscovery.cs b/Technosoftware/DaAeHdaClient.Com/OpcDiscovery.cs new file mode 100644 index 0000000..e9e27b6 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/OpcDiscovery.cs @@ -0,0 +1,204 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// Provides methods for discover (search) of OPC Servers. + public class OpcDiscovery + { + #region Fields + private static ServerEnumerator discovery_; + private static string hostName_; + private bool disposed_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// The finalizer implementation. + /// + ~OpcDiscovery() + { + Dispose(false); + } + + /// + /// Implements . + /// + public virtual void Dispose() + { + Dispose(true); + // Take yourself off the Finalization queue + // to prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!disposed_) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + discovery_?.Dispose(); + } + // Release unmanaged resources. If disposing is false, + // only the following code is executed. + } + disposed_ = true; + } + #endregion + + #region Public Methods (Host related) + /// + /// Returns a list of host names which could contain OPC servers. + /// + /// List of available network host names. + public static List GetHostNames() + { + return ComUtils.EnumComputers(); + } + #endregion + + #region Public Methods (Returns a list of OpcServer) + /// + /// Returns a list of servers that support the specified specification. + /// + /// Unique identifier for one OPC specification. + /// Returns a list of found OPC servers. + public static List GetServers(OpcSpecification specification) + { + var identity = new OpcUserIdentity("", ""); + return GetServers(specification, null, identity); + } + + /// + /// Returns a list of servers that support the specified specification. + /// + /// Unique identifier for one OPC specification. + /// The URL of the discovery server to be used. + /// Returns a list of found OPC servers. + public static List GetServers(OpcSpecification specification, string discoveryServerUrl) + { + var identity = new OpcUserIdentity("", ""); + return GetServers(specification, discoveryServerUrl, identity); + } + + /// + /// Returns a list of servers that support the specified specification. + /// + /// Unique identifier for one OPC specification. + /// The URL of the discovery server to be used. + /// The user identity to use when discovering the servers. + /// Returns a list of found OPC servers. + public static List GetServers(OpcSpecification specification, string discoveryServerUrl, OpcUserIdentity identity) + { + var serverList = new List(); + + var discovery = specification == OpcSpecification.OPC_AE_10 || (specification == OpcSpecification.OPC_DA_20 || + specification == OpcSpecification.OPC_DA_30) || specification == OpcSpecification.OPC_HDA_10; + + if (discovery) + { + if (discovery_ == null || hostName_ != discoveryServerUrl) + { + discovery_?.Dispose(); + hostName_ = discoveryServerUrl; + discovery_ = new ServerEnumerator(); + } + + var servers = discovery_.GetAvailableServers(specification); + + if (servers != null) + { + foreach (var server in servers) + { + serverList.Add(server); + } + } + } + + return serverList; + } + #endregion + + #region Public Methods (Returns OpcServer object for a specific URL) + /// + /// Creates a server object for the specified URL. + /// + /// The OpcUrl of the OPC server. + /// The OpcServer object. + public static OpcServer GetServer(OpcUrl url) + { + if (url == null) throw new ArgumentNullException(nameof(url)); + + OpcServer server = null; + + // create an unconnected server object for COM based servers. + + // DA + if (string.CompareOrdinal(url.Scheme, OpcUrlScheme.DA) == 0) + { + server = new Technosoftware.DaAeHdaClient.Da.TsCDaServer(new Factory(), url); + } + + // AE + else if (string.CompareOrdinal(url.Scheme, OpcUrlScheme.AE) == 0) + { + server = new Technosoftware.DaAeHdaClient.Ae.TsCAeServer(new Factory(), url); + } + + // HDA + else if (string.CompareOrdinal(url.Scheme, OpcUrlScheme.HDA) == 0) + { + server = new Technosoftware.DaAeHdaClient.Hda.TsCHdaServer(new Factory(), url); + } + + // Other specifications not supported yet. + if (server == null) + { + throw new NotSupportedException(url.Scheme); + } + + return server; + } + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/SafeNativeMethods.cs b/Technosoftware/DaAeHdaClient.Com/SafeNativeMethods.cs new file mode 100644 index 0000000..07d2bcc --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/SafeNativeMethods.cs @@ -0,0 +1,1883 @@ +#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.Reflection; +using System.Net; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Security; + +using Technosoftware.DaAeHdaClient.Utilities; +#endregion + +#pragma warning disable 618 + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// Exposes WIN32 and COM API functions. + /// + [SuppressUnmanagedCodeSecurityAttribute] + internal static class SafeNativeMethods + { + #region NetApi Function Declarations + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct SERVER_INFO_100 + { + public uint sv100_platform_id; + [MarshalAs(UnmanagedType.LPWStr)] + public string sv100_name; + } + + internal const uint LEVEL_SERVER_INFO_100 = 100; + internal const uint LEVEL_SERVER_INFO_101 = 101; + + internal const int MAX_PREFERRED_LENGTH = -1; + + internal const uint SV_TYPE_WORKSTATION = 0x00000001; + internal const uint SV_TYPE_SERVER = 0x00000002; + + [DllImport("Netapi32.dll")] + internal static extern int NetServerEnum( + IntPtr servername, + uint level, + out IntPtr bufptr, + int prefmaxlen, + out int entriesread, + out int totalentries, + uint servertype, + IntPtr domain, + IntPtr resume_handle); + + [DllImport("Netapi32.dll")] + internal static extern int NetApiBufferFree(IntPtr buffer); + + /// + /// Enumerates computers on the local network. + /// + public static List EnumComputers() + { + IntPtr pInfo; + int entriesRead; + int totalEntries; + var result = NetServerEnum( + IntPtr.Zero, + LEVEL_SERVER_INFO_100, + out pInfo, + MAX_PREFERRED_LENGTH, + out entriesRead, + out totalEntries, + SV_TYPE_WORKSTATION | SV_TYPE_SERVER, + IntPtr.Zero, + IntPtr.Zero); + + if (result != 0) + { + throw new ApplicationException("NetApi Error = " + string.Format("0x{0:X8}", result)); + } + + var computers = new List(); + + var pos = pInfo; + + for (var ii = 0; ii < entriesRead; ii++) + { + var info = (SERVER_INFO_100)Marshal.PtrToStructure(pos, typeof(SERVER_INFO_100)); + + computers.Add(info.sv100_name); + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(SERVER_INFO_100))); + } + + NetApiBufferFree(pInfo); + + return computers; + } + #endregion + + #region OLE32 Function/Interface Declarations + internal const int MAX_MESSAGE_LENGTH = 1024; + + internal const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + internal const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + [DllImport("Kernel32.dll")] + internal static extern int FormatMessageW( + int dwFlags, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + IntPtr lpBuffer, + int nSize, + IntPtr Arguments); + + [DllImport("Kernel32.dll")] + internal static extern int GetSystemDefaultLangID(); + + [DllImport("Kernel32.dll")] + internal static extern int GetUserDefaultLangID(); + + /// + /// The WIN32 system default locale. + /// + public const int LOCALE_SYSTEM_DEFAULT = 0x800; + + /// + /// The WIN32 user default locale. + /// + public const int LOCALE_USER_DEFAULT = 0x400; + + /// + /// The base for the WIN32 FILETIME structure. + /// + internal static readonly DateTime FILETIME_BaseTime = new DateTime(1601, 1, 1); + + /// + /// WIN32 GUID struct declaration. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct GUID + { + public int Data1; + public short Data2; + public short Data3; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Data4; + } + + /// + /// The size, in bytes, of a VARIANT structure. + /// + internal static int VARIANT_SIZE => (IntPtr.Size > 4) ? 0x18 : 0x10; + + [DllImport("OleAut32.dll")] + internal static extern int VariantChangeTypeEx( + IntPtr pvargDest, + IntPtr pvarSrc, + int lcid, + ushort wFlags, + short vt); + + /// + /// Intializes a pointer to a VARIANT. + /// + [DllImport("oleaut32.dll")] + internal static extern void VariantInit(IntPtr pVariant); + + /// + /// Frees all memory referenced by a VARIANT stored in unmanaged memory. + /// + [DllImport("oleaut32.dll")] + internal static extern int VariantClear(IntPtr pVariant); + + internal const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005 + internal const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A + + internal const int VARIANT_NOVALUEPROP = 0x01; + internal const int VARIANT_ALPHABOOL = 0x02; // For VT_BOOL to VT_BSTR conversions convert to "True"/"False" instead of + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct SOLE_AUTHENTICATION_SERVICE + { + public uint dwAuthnSvc; + public uint dwAuthzSvc; + [MarshalAs(UnmanagedType.LPWStr)] + public string pPrincipalName; + public int hr; + } + + internal const uint RPC_C_AUTHN_NONE = 0; + internal const uint RPC_C_AUTHN_DCE_PRIVATE = 1; + internal const uint RPC_C_AUTHN_DCE_PUBLIC = 2; + internal const uint RPC_C_AUTHN_DEC_PUBLIC = 4; + internal const uint RPC_C_AUTHN_GSS_NEGOTIATE = 9; + internal const uint RPC_C_AUTHN_WINNT = 10; + internal const uint RPC_C_AUTHN_GSS_SCHANNEL = 14; + internal const uint RPC_C_AUTHN_GSS_KERBEROS = 16; + internal const uint RPC_C_AUTHN_DPA = 17; + internal const uint RPC_C_AUTHN_MSN = 18; + internal const uint RPC_C_AUTHN_DIGEST = 21; + internal const uint RPC_C_AUTHN_MQ = 100; + internal const uint RPC_C_AUTHN_DEFAULT = 0xFFFFFFFF; + + internal const uint RPC_C_AUTHZ_NONE = 0; + internal const uint RPC_C_AUTHZ_NAME = 1; + internal const uint RPC_C_AUTHZ_DCE = 2; + internal const uint RPC_C_AUTHZ_DEFAULT = 0xffffffff; + + internal const uint RPC_C_AUTHN_LEVEL_DEFAULT = 0; + internal const uint RPC_C_AUTHN_LEVEL_NONE = 1; + internal const uint RPC_C_AUTHN_LEVEL_CONNECT = 2; + internal const uint RPC_C_AUTHN_LEVEL_CALL = 3; + internal const uint RPC_C_AUTHN_LEVEL_PKT = 4; + internal const uint RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5; + internal const uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; + + internal const uint RPC_C_IMP_LEVEL_ANONYMOUS = 1; + internal const uint RPC_C_IMP_LEVEL_IDENTIFY = 2; + internal const uint RPC_C_IMP_LEVEL_IMPERSONATE = 3; + internal const uint RPC_C_IMP_LEVEL_DELEGATE = 4; + + internal const uint EOAC_NONE = 0x00; + internal const uint EOAC_MUTUAL_AUTH = 0x01; + internal const uint EOAC_CLOAKING = 0x10; + internal const uint EOAC_STATIC_CLOAKING = 0x20; + internal const uint EOAC_DYNAMIC_CLOAKING = 0x40; + internal const uint EOAC_SECURE_REFS = 0x02; + internal const uint EOAC_ACCESS_CONTROL = 0x04; + internal const uint EOAC_APPID = 0x08; + + [DllImport("ole32.dll")] + internal static extern int CoInitializeSecurity( + IntPtr pSecDesc, + int cAuthSvc, + SOLE_AUTHENTICATION_SERVICE[] asAuthSvc, + IntPtr pReserved1, + uint dwAuthnLevel, + uint dwImpLevel, + IntPtr pAuthList, + uint dwCapabilities, + IntPtr pReserved3); + + [DllImport("ole32.dll")] + private static extern int CoQueryProxyBlanket( + [MarshalAs(UnmanagedType.IUnknown)] + object pProxy, + ref uint pAuthnSvc, + ref uint pAuthzSvc, + [MarshalAs(UnmanagedType.LPWStr)] + ref string pServerPrincName, + ref uint pAuthnLevel, + ref uint pImpLevel, + ref IntPtr pAuthInfo, + ref uint pCapabilities); + + [DllImport("ole32.dll")] + private static extern int CoSetProxyBlanket( + [MarshalAs(UnmanagedType.IUnknown)] + object pProxy, + uint pAuthnSvc, + uint pAuthzSvc, + IntPtr pServerPrincName, + uint pAuthnLevel, + uint pImpLevel, + IntPtr pAuthInfo, + uint pCapabilities); + + private static readonly IntPtr COLE_DEFAULT_PRINCIPAL = new IntPtr(-1); + private static readonly IntPtr COLE_DEFAULT_AUTHINFO = new IntPtr(-1); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COSERVERINFO + { + public uint dwReserved1; + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszName; + public IntPtr pAuthInfo; + public uint dwReserved2; + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COAUTHINFO + { + public uint dwAuthnSvc; + public uint dwAuthzSvc; + public IntPtr pwszServerPrincName; + public uint dwAuthnLevel; + public uint dwImpersonationLevel; + public IntPtr pAuthIdentityData; + public uint dwCapabilities; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COAUTHIDENTITY + { + public IntPtr User; + public uint UserLength; + public IntPtr Domain; + public uint DomainLength; + public IntPtr Password; + public uint PasswordLength; + public uint Flags; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MULTI_QI + { + public IntPtr iid; + [MarshalAs(UnmanagedType.IUnknown)] + public object pItf; + public uint hr; + } + + internal const uint CLSCTX_INPROC_SERVER = 0x1; + internal const uint CLSCTX_INPROC_HANDLER = 0x2; + internal const uint CLSCTX_LOCAL_SERVER = 0x4; + internal const uint CLSCTX_REMOTE_SERVER = 0x10; + internal const uint CLSCTX_DISABLE_AAA = 0x8000; + + private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); + + internal const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1; + internal const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2; + + [DllImport("ole32.dll")] + private static extern int CoCreateInstanceEx( + ref Guid clsid, + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + uint dwClsCtx, + [In] + ref COSERVERINFO pServerInfo, + uint dwCount, + [In, Out] + MULTI_QI[] pResults); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct LICINFO + { + public int cbLicInfo; + [MarshalAs(UnmanagedType.Bool)] + public bool fRuntimeKeyAvail; + [MarshalAs(UnmanagedType.Bool)] + public bool fLicVerified; + } + + [ComImport] + [GuidAttribute("00000001-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClassFactory + { + void CreateInstance( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.Interface)] + [Out] out object ppvObject); + + void LockServer( + [MarshalAs(UnmanagedType.Bool)] + bool fLock); + } + + [ComImport] + [GuidAttribute("B196B28F-BAB4-101A-B69C-00AA00341D07")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClassFactory2 + { + void CreateInstance( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.Interface)] + [Out] out object ppvObject); + + void LockServer( + [MarshalAs(UnmanagedType.Bool)] + bool fLock); + + void GetLicInfo( + [In, Out] ref LICINFO pLicInfo); + + void RequestLicKey( + int dwReserved, + [MarshalAs(UnmanagedType.BStr)] + string pbstrKey); + + void CreateInstanceLic( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.IUnknown)] + object punkReserved, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.BStr)] + string bstrKey, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppvObject); + } + + [DllImport("ole32.dll")] + private static extern int CoGetClassObject( + [MarshalAs(UnmanagedType.LPStruct)] + Guid clsid, + uint dwClsContext, + [In] ref COSERVERINFO pServerInfo, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppv); + + internal const int LOGON32_PROVIDER_DEFAULT = 0; + internal const int LOGON32_LOGON_INTERACTIVE = 2; + internal const int LOGON32_LOGON_NETWORK = 3; + + internal const int SECURITY_ANONYMOUS = 0; + internal const int SECURITY_IDENTIFICATION = 1; + internal const int SECURITY_IMPERSONATION = 2; + internal const int SECURITY_DELEGATION = 3; + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool LogonUser( + string lpszUsername, + string lpszDomain, + string lpszPassword, + int dwLogonType, + int dwLogonProvider, + ref IntPtr phToken); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + private extern static bool CloseHandle(IntPtr handle); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private extern static bool DuplicateToken( + IntPtr ExistingTokenHandle, + int SECURITY_IMPERSONATION_LEVEL, + ref IntPtr DuplicateTokenHandle); + #endregion + + #region ServerInfo Class + /// + /// A class used to allocate and deallocate the elements of a COSERVERINFO structure. + /// + class ServerInfo + { + #region Public Interface + /// + /// Allocates a COSERVERINFO structure. + /// + public COSERVERINFO Allocate(string hostName, OpcUserIdentity identity) + { + // initialize server info structure. + var serverInfo = new COSERVERINFO(); + + serverInfo.pwszName = hostName; + serverInfo.pAuthInfo = IntPtr.Zero; + serverInfo.dwReserved1 = 0; + serverInfo.dwReserved2 = 0; + + // no authentication for default identity + if (OpcUserIdentity.IsDefault(identity)) + { + return serverInfo; + } + + m_hUserName = GCHandle.Alloc(identity.Username, GCHandleType.Pinned); + m_hPassword = GCHandle.Alloc(identity.Password, GCHandleType.Pinned); + m_hDomain = GCHandle.Alloc(identity.Domain, GCHandleType.Pinned); + + m_hIdentity = new GCHandle(); + + // create identity structure. + var authIdentity = new COAUTHIDENTITY(); + + authIdentity.User = m_hUserName.AddrOfPinnedObject(); + authIdentity.UserLength = (uint)((identity.Username != null) ? identity.Username.Length : 0); + authIdentity.Password = m_hPassword.AddrOfPinnedObject(); + authIdentity.PasswordLength = (uint)((identity.Password != null) ? identity.Password.Length : 0); + authIdentity.Domain = m_hDomain.AddrOfPinnedObject(); + authIdentity.DomainLength = (uint)((identity.Domain != null) ? identity.Domain.Length : 0); + authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + m_hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); + + // create authorization info structure. + var authInfo = new COAUTHINFO(); + + authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; + authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; + authInfo.pwszServerPrincName = IntPtr.Zero; + authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; + authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + authInfo.pAuthIdentityData = m_hIdentity.AddrOfPinnedObject(); + authInfo.dwCapabilities = EOAC_NONE; // EOAC_DYNAMIC_CLOAKING; + + m_hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned); + + // update server info structure. + serverInfo.pAuthInfo = m_hAuthInfo.AddrOfPinnedObject(); + + return serverInfo; + } + + /// + /// Deallocated memory allocated when the COSERVERINFO structure was created. + /// + public void Deallocate() + { + if (m_hUserName.IsAllocated) m_hUserName.Free(); + if (m_hPassword.IsAllocated) m_hPassword.Free(); + if (m_hDomain.IsAllocated) m_hDomain.Free(); + if (m_hIdentity.IsAllocated) m_hIdentity.Free(); + if (m_hAuthInfo.IsAllocated) m_hAuthInfo.Free(); + } + #endregion + + #region Private Members + private GCHandle m_hUserName; + private GCHandle m_hPassword; + private GCHandle m_hDomain; + private GCHandle m_hIdentity; + private GCHandle m_hAuthInfo; + #endregion + } + #endregion + + #region Initialization Functions + /// + /// Initializes COM security. + /// + public static void InitializeSecurity() + { + var error = CoInitializeSecurity( + IntPtr.Zero, + -1, + null, + IntPtr.Zero, + RPC_C_AUTHN_LEVEL_CONNECT, + RPC_C_IMP_LEVEL_IMPERSONATE, + IntPtr.Zero, + EOAC_DYNAMIC_CLOAKING, + IntPtr.Zero); + + // this call will fail in the debugger if the + // 'Debug | Enable Visual Studio Hosting Process' + // option is checked in the project properties. + if (error != 0) + { + // throw new ExternalException("CoInitializeSecurity: " + GetSystemMessage(error), error); + } + } + + /// + /// Determines if the host is the local host. + /// + private static bool IsLocalHost(string hostName) + { + // lookup requested host. + var requestedHost = Dns.GetHostEntry(hostName); + + if (requestedHost == null || requestedHost.AddressList == null) + { + return true; + } + + // check for loopback. + for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) + { + var requestedIP = requestedHost.AddressList[ii]; + + if (requestedIP == null || requestedIP.Equals(IPAddress.Loopback)) + { + return true; + } + } + + // lookup local host. + var localHost = Dns.GetHostEntry(Dns.GetHostName()); + + if (localHost == null || localHost.AddressList == null) + { + return false; + } + + // check for localhost. + for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) + { + var requestedIP = requestedHost.AddressList[ii]; + + for (var jj = 0; jj < localHost.AddressList.Length; jj++) + { + if (requestedIP.Equals(localHost.AddressList[jj])) + { + return true; + } + } + } + + // must be remote. + return false; + } + + // COM impersonation is a nice feature but variations between behavoirs on different + // windows platforms make it virtually impossible to support. This code is left here + // in case it becomes a critical requirement in the future. +#if COM_IMPERSONATION_SUPPORT + /// + /// Returns the WindowsIdentity associated with a UserIdentity. + /// + public static WindowsPrincipal GetPrincipalFromUserIdentity(UserIdentity user) + { + if (UserIdentity.IsDefault(user)) + { + return null; + } + + // validate the credentials. + IntPtr token = IntPtr.Zero; + + bool result = LogonUser( + user.Username, + user.Domain, + user.Password, + LOGON32_LOGON_NETWORK, + LOGON32_PROVIDER_DEFAULT, + ref token); + + if (!result) + { + throw ServiceResultException.Create( + StatusCodes.BadIdentityTokenRejected, + "Could not logon as user '{0}'. Reason: {1}.", + user.Username, + GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT)); + } + + try + { + // create the windows identity. + WindowsIdentity identity = new WindowsIdentity(token); + + // validate the identity. + identity.Impersonate(); + + // return a principal. + return new WindowsPrincipal(identity); + } + finally + { + CloseHandle(token); + } + } + + /// + /// Sets the security settings for the proxy. + /// + public static void SetProxySecurity(object server, UserIdentity user) + { + // allocate the + GCHandle hUserName = GCHandle.Alloc(user.Username, GCHandleType.Pinned); + GCHandle hPassword = GCHandle.Alloc(user.Password, GCHandleType.Pinned); + GCHandle hDomain = GCHandle.Alloc(user.Domain, GCHandleType.Pinned); + + GCHandle hIdentity = new GCHandle(); + + // create identity structure. + COAUTHIDENTITY authIdentity = new COAUTHIDENTITY(); + + authIdentity.User = hUserName.AddrOfPinnedObject(); + authIdentity.UserLength = (uint)((user.Username != null) ? user.Username.Length : 0); + authIdentity.Password = hPassword.AddrOfPinnedObject(); + authIdentity.PasswordLength = (uint)((user.Password != null) ? user.Password.Length : 0); + authIdentity.Domain = hDomain.AddrOfPinnedObject(); + authIdentity.DomainLength = (uint)((user.Domain != null) ? user.Domain.Length : 0); + authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); + + try + { + SetProxySecurity(server, hIdentity.AddrOfPinnedObject()); + } + finally + { + hUserName.Free(); + hPassword.Free(); + hDomain.Free(); + hIdentity.Free(); + } + } + + /// + /// Sets the security settings for the proxy. + /// + public static void SetProxySecurity(object server, IntPtr pAuthInfo) + { + // get the existing proxy settings. + uint pAuthnSvc = 0; + uint pAuthzSvc = 0; + string pServerPrincName = ""; + uint pAuthnLevel = 0; + uint pImpLevel = 0; + IntPtr pAuthInfo2 = IntPtr.Zero; + uint pCapabilities = 0; + + CoQueryProxyBlanket( + server, + ref pAuthnSvc, + ref pAuthzSvc, + ref pServerPrincName, + ref pAuthnLevel, + ref pImpLevel, + ref pAuthInfo2, + ref pCapabilities); + + pAuthnSvc = RPC_C_AUTHN_WINNT; + pAuthzSvc = RPC_C_AUTHZ_NONE; + pAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; + pImpLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + pCapabilities = EOAC_DYNAMIC_CLOAKING; + + // update proxy security settings. + CoSetProxyBlanket( + server, + pAuthnSvc, + pAuthzSvc, + COLE_DEFAULT_PRINCIPAL, + pAuthnLevel, + pImpLevel, + pAuthInfo, + pCapabilities); + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstance2(Guid clsid, string hostName, UserIdentity identity) + { + // validate the host name before proceeding (exception thrown if host is not valid). + bool isLocalHost = IsLocalHost(hostName); + + // allocate the connection info. + ServerInfo serverInfo = new ServerInfo(); + COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); + object instance = null; + IClassFactory factory = null; + + try + { + // create the factory. + object server = null; + + CoGetClassObject( + clsid, + (isLocalHost)?CLSCTX_LOCAL_SERVER:CLSCTX_REMOTE_SERVER, + ref coserverInfo, + IID_IUnknown, + out server); + + // SetProxySecurity(server, coserverInfo.pAuthInfo); + + factory = (IClassFactory)server; + + // check for valid factory. + if (factory == null) + { + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory for COM server '{0}' on host '{1}'.", clsid, hostName); + } + + // SetProxySecurity(factory, coserverInfo.pAuthInfo); + + factory.CreateInstance(null, IID_IUnknown, out instance); + + // SetProxySecurity(instance, coserverInfo.pAuthInfo); + } + finally + { + serverInfo.Deallocate(); + } + + return instance; + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstanceWithLicenseKey(Guid clsid, string hostName, UserIdentity identity, string licenseKey) + { + ServerInfo serverInfo = new ServerInfo(); + COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); + object instance = null; + IClassFactory2 factory = null; + + try + { + // check whether connecting locally or remotely. + uint clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; + + if (hostName != null && hostName.Length > 0) + { + clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + } + + // get the class factory. + object server = null; + + CoGetClassObject( + clsid, + clsctx, + ref coserverInfo, + typeof(IClassFactory2).GUID, + out server); + + // SetProxySecurity(server, coserverInfo.pAuthInfo); + + factory = (IClassFactory2)server; + + // check for valid factory. + if (factory == null) + { + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory2 for COM server '{0}' on host '{1}'.", clsid, hostName); + } + + // SetProxySecurity(factory, coserverInfo.pAuthInfo); + + // create instance. + factory.CreateInstanceLic( + null, + null, + IID_IUnknown, + licenseKey, + out instance); + + // SetProxySecurity(instance, coserverInfo.pAuthInfo); + } + finally + { + serverInfo.Deallocate(); + SafeNativeMethods.ReleaseServer(factory); + } + + return instance; + } +#endif + #endregion + + #region Conversion Functions + + /// + /// Tests if the specified string matches the specified pattern. + /// + public static bool Match(string target, string pattern, bool caseSensitive) + { + // an empty pattern always matches. + if (pattern == null || pattern.Length == 0) + { + return true; + } + + // an empty string never matches. + if (target == null || target.Length == 0) + { + return false; + } + + // check for exact match + if (caseSensitive) + { + if (target == pattern) + { + return true; + } + } + else + { + if (target.ToLower() == pattern.ToLower()) + { + return true; + } + } + + char c; + char p; + char l; + + var pIndex = 0; + var tIndex = 0; + + while (tIndex < target.Length && pIndex < pattern.Length) + { + p = ConvertCase(pattern[pIndex++], caseSensitive); + + if (pIndex > pattern.Length) + { + return (tIndex >= target.Length); // if end of string true + } + + switch (p) + { + // match zero or more char. + case '*': + { + while (pIndex < pattern.Length && pattern[pIndex] == '*') + { + pIndex++; + } + + while (tIndex < target.Length) + { + if (Match(target.Substring(tIndex++), pattern.Substring(pIndex), caseSensitive)) + { + return true; + } + } + + return Match(target, pattern.Substring(pIndex), caseSensitive); + } + + // match any one char. + case '?': + { + // check if end of string when looking for a single character. + if (tIndex >= target.Length) + { + return false; + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length - 1) + { + return false; + } + + tIndex++; + break; + } + + // match char set + case '[': + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (tIndex > target.Length) + { + return false; // syntax + } + + l = '\0'; + + // match a char if NOT in set [] + if (pattern[pIndex] == '!') + { + ++pIndex; + + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then + { + break; // no match found + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + return false; // if in range, return false + } + } + + l = p; + + if (c == p) // if char matches this element + { + return false; // return false + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + } + + // match if char is in set [] + else + { + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then no match found + { + return false; + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + break; // if in range, move on + } + } + + l = p; + + if (c == p) // if char matches this element move on + { + break; + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + + while (pIndex < pattern.Length && p != ']') // got a match in char set skip to end of set + { + p = pattern[pIndex++]; + } + } + + break; + } + + // match digit. + case '#': + { + c = target[tIndex++]; + + if (!char.IsDigit(c)) + { + return false; // not a digit + } + + break; + } + + // match exact char. + default: + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (c != p) // check for exact char + { + return false; // not a match + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length - 1) + { + return false; + } + + break; + } + } + } + + return true; + } + + // ConvertCase + private static char ConvertCase(char c, bool caseSensitive) + { + return (caseSensitive) ? c : char.ToUpper(c); + } + + /// + /// Unmarshals and frees an array of HRESULTs. + /// + public static int[] GetStatusCodes(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + // unmarshal HRESULT array. + var output = new int[size]; + Marshal.Copy(pArray, output, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return output; + } + + /// + /// Unmarshals and frees an array of 32 bit integers. + /// + public static int[] GetInt32s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new int[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Unmarshals and frees an array of 32 bit integers. + /// + public static int[] GetUInt32s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new int[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + + /// + /// Allocates and marshals an array of 32 bit integers. + /// + public static IntPtr GetInt32s(int[] input) + { + var output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Unmarshals and frees a array of 16 bit integers. + /// + public static short[] GetInt16s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new short[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Allocates and marshals an array of 16 bit integers. + /// + public static IntPtr GetInt16s(short[] input) + { + var output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(short)) * input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Marshals an array of strings into a unmanaged memory buffer + /// + /// The array of strings to marshal + /// The pointer to the unmanaged memory buffer + public static IntPtr GetUnicodeStrings(string[] values) + { + var size = (values != null) ? values.Length : 0; + + if (size <= 0) + { + return IntPtr.Zero; + } + + var pointers = new int[size]; + + for (var ii = 0; ii < size; ii++) + { + pointers[ii] = (int)Marshal.StringToCoTaskMemUni(values[ii]); + } + + var pValues = Marshal.AllocCoTaskMem(values.Length * Marshal.SizeOf(typeof(IntPtr))); + Marshal.Copy(pointers, 0, pValues, size); + + return pValues; + } + + /// + /// Unmarshals and frees a array of unicode strings. + /// + public static string[] GetUnicodeStrings(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var pointers = new IntPtr[size]; + Marshal.Copy(pArray, pointers, 0, size); + + var strings = new string[size]; + + for (var ii = 0; ii < size; ii++) + { + var pString = pointers[ii]; + strings[ii] = Marshal.PtrToStringUni(pString); + if (deallocate) Marshal.FreeCoTaskMem(pString); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return strings; + } + + /// + /// Marshals a DateTime as a WIN32 FILETIME. + /// + /// The DateTime object to marshal + /// The WIN32 FILETIME + public static System.Runtime.InteropServices.ComTypes.FILETIME GetFILETIME(DateTime datetime) + { + System.Runtime.InteropServices.ComTypes.FILETIME filetime; + + if (datetime <= FILETIME_BaseTime) + { + filetime.dwHighDateTime = 0; + filetime.dwLowDateTime = 0; + return filetime; + } + // adjust for WIN32 FILETIME base. + var ticks = datetime.Subtract(new TimeSpan(FILETIME_BaseTime.Ticks)).Ticks; + + filetime.dwHighDateTime = (int)((ticks >> 32) & 0xFFFFFFFF); + filetime.dwLowDateTime = (int)(ticks & 0xFFFFFFFF); + + return filetime; + } + + /// + /// Unmarshals an array of FILETIMEs + /// + public static System.Runtime.InteropServices.ComTypes.FILETIME[] GetFILETIMEs(ref IntPtr pArray, int size) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var values = new System.Runtime.InteropServices.ComTypes.FILETIME[size]; + + var pos = pArray; + + for (var ii = 0; ii < size; ii++) + { + try + { + values[ii] = (System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(pos, typeof(System.Runtime.InteropServices.ComTypes.FILETIME)); + } + catch (Exception) + { + } + + pos = (IntPtr)(pos.ToInt64() + 8); + } + + + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + + return values; + } + + /// + /// Unmarshals a WIN32 FILETIME from a pointer. + /// + /// A pointer to a FILETIME structure. + /// A DateTime object. + public static DateTime GetDateTime(IntPtr pFiletime) + { + if (pFiletime == IntPtr.Zero) + { + return DateTime.MinValue; + } + + return GetDateTime((System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(pFiletime, typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + /// + /// Unmarshals a WIN32 FILETIME. + /// + public static DateTime GetDateTime(System.Runtime.InteropServices.ComTypes.FILETIME filetime) + { + // check for invalid value. + if (filetime.dwHighDateTime < 0) + { + return DateTime.MinValue; + } + + // convert FILETIME structure to a 64 bit integer. + var buffer = (long)filetime.dwHighDateTime; + + if (buffer < 0) + { + buffer += ((long)uint.MaxValue + 1); + } + + var ticks = (buffer << 32); + + buffer = (long)filetime.dwLowDateTime; + + if (buffer < 0) + { + buffer += ((long)uint.MaxValue + 1); + } + + ticks += buffer; + + // check for invalid value. + if (ticks == 0) + { + return DateTime.MinValue; + } + + // adjust for WIN32 FILETIME base. + return FILETIME_BaseTime.Add(new TimeSpan(ticks)); + } + + /// + /// Marshals an array of DateTimes into an unmanaged array of FILETIMEs + /// + /// The array of DateTimes to marshal + /// The IntPtr array of FILETIMEs + public static IntPtr GetFILETIMEs(DateTime[] datetimes) + { + var count = (datetimes != null) ? datetimes.Length : 0; + + if (count <= 0) + { + return IntPtr.Zero; + } + + var pFiletimes = Marshal.AllocCoTaskMem(count * Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + + var pos = pFiletimes; + + for (var ii = 0; ii < count; ii++) + { + Marshal.StructureToPtr(GetFILETIME(datetimes[ii]), pos, false); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + return pFiletimes; + } + + /// + /// Unmarshals an array of WIN32 FILETIMEs as DateTimes. + /// + public static DateTime[] GetDateTimes(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var datetimes = new DateTime[size]; + + var pos = pArray; + + for (var ii = 0; ii < size; ii++) + { + datetimes[ii] = GetDateTime(pos); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return datetimes; + } + + /// + /// Unmarshals an array of WIN32 GUIDs as Guid. + /// + public static Guid[] GetGUIDs(ref IntPtr pInput, int size, bool deallocate) + { + if (pInput == IntPtr.Zero || size <= 0) + { + return null; + } + + var guids = new Guid[size]; + + var pos = pInput; + + for (var ii = 0; ii < size; ii++) + { + var input = (GUID)Marshal.PtrToStructure(pInput, typeof(GUID)); + + guids[ii] = new Guid(input.Data1, input.Data2, input.Data3, input.Data4); + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(GUID))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + + return guids; + } + + + /// + /// Converts a LCID to a Locale string. + /// + public static string GetLocale(int input) + { + try + { + if (input == LOCALE_SYSTEM_DEFAULT || input == LOCALE_USER_DEFAULT || input == 0) + { + return CultureInfo.InvariantCulture.Name; + } + + return new CultureInfo(input).Name; + } + catch (Exception e) + { + throw new OpcResultException(OpcResult.E_FAIL, "Unrecognized locale provided.", e); + } + } + + /// + /// Converts a Locale string to a LCID. + /// + public static int GetLocale(string input) + { + // check for the default culture. + if (input == null || input == "") + { + return 0; + } + + CultureInfo locale; + try { locale = new CultureInfo(input); } + catch { locale = CultureInfo.CurrentCulture; } + + return locale.LCID; + } + + /// + /// Converts the VARTYPE to a SystemType + /// + public static Type GetSystemType(short input) + { + var isArray = (((short)VarEnum.VT_ARRAY & input) != 0); + var varType = (VarEnum)Enum.ToObject(typeof(VarEnum), (input & ~(short)VarEnum.VT_ARRAY)); + + if (!isArray) + { + switch (varType) + { + case VarEnum.VT_I1: return typeof(sbyte); + case VarEnum.VT_UI1: return typeof(byte); + case VarEnum.VT_I2: return typeof(short); + case VarEnum.VT_UI2: return typeof(ushort); + case VarEnum.VT_I4: return typeof(int); + case VarEnum.VT_UI4: return typeof(uint); + case VarEnum.VT_I8: return typeof(long); + case VarEnum.VT_UI8: return typeof(ulong); + case VarEnum.VT_R4: return typeof(float); + case VarEnum.VT_R8: return typeof(double); + case VarEnum.VT_BOOL: return typeof(bool); + case VarEnum.VT_DATE: return typeof(DateTime); + case VarEnum.VT_BSTR: return typeof(string); + case VarEnum.VT_CY: return typeof(decimal); + case VarEnum.VT_EMPTY: return typeof(object); + case VarEnum.VT_VARIANT: return typeof(object); + } + } + else + { + switch (varType) + { + case VarEnum.VT_I1: return typeof(sbyte[]); + case VarEnum.VT_UI1: return typeof(byte[]); + case VarEnum.VT_I2: return typeof(short[]); + case VarEnum.VT_UI2: return typeof(ushort[]); + case VarEnum.VT_I4: return typeof(int[]); + case VarEnum.VT_UI4: return typeof(uint[]); + case VarEnum.VT_I8: return typeof(long[]); + case VarEnum.VT_UI8: return typeof(ulong[]); + case VarEnum.VT_R4: return typeof(float[]); + case VarEnum.VT_R8: return typeof(double[]); + case VarEnum.VT_BOOL: return typeof(bool[]); + case VarEnum.VT_DATE: return typeof(DateTime[]); + case VarEnum.VT_BSTR: return typeof(string[]); + case VarEnum.VT_CY: return typeof(decimal[]); + case VarEnum.VT_EMPTY: return typeof(object[]); + case VarEnum.VT_VARIANT: return typeof(object[]); + } + } + + return null; + } + + /// + /// Returns the VARTYPE for the value. + /// + public static VarEnum GetVarType(object input) + { + if (input == null) + { + return VarEnum.VT_EMPTY; + } + + return GetVarType(input.GetType()); + } + + /// + /// Converts the system type to a VARTYPE. + /// + public static VarEnum GetVarType(Type type) + { + if (type == null) + { + return VarEnum.VT_EMPTY; + } + + if (type == null) return VarEnum.VT_EMPTY; + if (type == typeof(sbyte)) return VarEnum.VT_I1; + if (type == typeof(byte)) return VarEnum.VT_UI1; + if (type == typeof(short)) return VarEnum.VT_I2; + if (type == typeof(ushort)) return VarEnum.VT_UI2; + if (type == typeof(int)) return VarEnum.VT_I4; + if (type == typeof(uint)) return VarEnum.VT_UI4; + if (type == typeof(long)) return VarEnum.VT_I8; + if (type == typeof(ulong)) return VarEnum.VT_UI8; + if (type == typeof(float)) return VarEnum.VT_R4; + if (type == typeof(double)) return VarEnum.VT_R8; + if (type == typeof(decimal)) return VarEnum.VT_CY; + if (type == typeof(bool)) return VarEnum.VT_BOOL; + if (type == typeof(DateTime)) return VarEnum.VT_DATE; + if (type == typeof(string)) return VarEnum.VT_BSTR; + if (type == typeof(sbyte[])) return VarEnum.VT_ARRAY | VarEnum.VT_I1; + if (type == typeof(byte[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI1; + if (type == typeof(short[])) return VarEnum.VT_ARRAY | VarEnum.VT_I2; + if (type == typeof(ushort[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI2; + if (type == typeof(int[])) return VarEnum.VT_ARRAY | VarEnum.VT_I4; + if (type == typeof(uint[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI4; + if (type == typeof(long[])) return VarEnum.VT_ARRAY | VarEnum.VT_I8; + if (type == typeof(ulong[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI8; + if (type == typeof(float[])) return VarEnum.VT_ARRAY | VarEnum.VT_R4; + if (type == typeof(double[])) return VarEnum.VT_ARRAY | VarEnum.VT_R8; + if (type == typeof(decimal[])) return VarEnum.VT_ARRAY | VarEnum.VT_CY; + if (type == typeof(bool[])) return VarEnum.VT_ARRAY | VarEnum.VT_BOOL; + if (type == typeof(DateTime[])) return VarEnum.VT_ARRAY | VarEnum.VT_DATE; + if (type == typeof(string[])) return VarEnum.VT_ARRAY | VarEnum.VT_BSTR; + if (type == typeof(object[])) return VarEnum.VT_ARRAY | VarEnum.VT_VARIANT; + + return VarEnum.VT_EMPTY; + } + + /// + /// Returns true is the object is a valid COM type. + /// + public static bool IsValidComType(object input) + { + var array = input as Array; + + if (array != null) + { + foreach (var value in array) + { + if (!IsValidComType(value)) + { + return false; + } + } + + return true; + } + + return GetVarType(input) != VarEnum.VT_EMPTY; + } + + /// + /// Returns the symbolic name for the specified error. + /// + public static string GetErrorText(Type type, int error) + { + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static); + + foreach (var field in fields) + { + if (error == (int)field.GetValue(type)) + { + return field.Name; + } + } + + return string.Format("0x{0:X8}", error); + } + + /// + /// Gets the error code for the exception. + /// + /// The exception. + /// The default code. + /// The error code + /// This method ignores the exception but makes it possible to keep track of ignored exceptions. + public static int GetErrorCode(Exception e, int defaultCode) + { + return defaultCode; + } + + /// + /// Releases the server if it is a true COM server. + /// + public static void ReleaseServer(object server) + { + if (server != null && server.GetType().IsCOMObject) + { + Marshal.ReleaseComObject(server); + } + } + + /// + /// Retrieves the system message text for the specified error. + /// + public static string GetSystemMessage(int error, int localeId) + { + int langId; + switch (localeId) + { + case LOCALE_SYSTEM_DEFAULT: + { + langId = GetSystemDefaultLangID(); + break; + } + + case LOCALE_USER_DEFAULT: + { + langId = GetUserDefaultLangID(); + break; + } + + default: + { + langId = (0xFFFF & localeId); + break; + } + } + + var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH); + + var result = FormatMessageW( + (int)FORMAT_MESSAGE_FROM_SYSTEM, + IntPtr.Zero, + error, + langId, + buffer, + MAX_MESSAGE_LENGTH - 1, + IntPtr.Zero); + + if (result > 0) + { + var msg = Marshal.PtrToStringUni(buffer); + Marshal.FreeCoTaskMem(buffer); + + if (msg != null && msg.Length > 0) + { + return msg.Trim(); + } + } + + return string.Format("0x{0:X8}", error); + } + + /// + /// Converts an exception to an exception that returns a COM error code. + /// + public static Exception CreateComException(Exception e, int errorId) + { + return new COMException(e.Message, errorId); + } + + /// + /// Creates a COM exception. + /// + public static Exception CreateComException(string message, int errorId) + { + return new COMException(message, errorId); + } + + /// + /// Converts an exception to an exception that returns a COM error code. + /// + public static Exception CreateComException(int errorId) + { + return new COMException(string.Format("0x{0:X8}", errorId), errorId); + } + + /// + /// Checks if the error is an RPC error. + /// + public static bool IsRpcError(Exception e) + { + var error = Marshal.GetHRForException(e); + + // Assume that any 0x8007 is a fatal communication error. + // May need to update this check if the assumption proves to be incorrect. + if ((error & 0xFFFF0000) == 0x80070000) + { + error &= 0xFFFF; + + // check the RPC error range define in WinError.h + if (error >= 1700 && error < 1918) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the error for the exception is one of the recognized errors. + /// + public static bool IsUnknownError(Exception e, params int[] knownErrors) + { + var error = Marshal.GetHRForException(e); + + if (knownErrors != null) + { + for (var ii = 0; ii < knownErrors.Length; ii++) + { + if (knownErrors[ii] == error) + { + return false; + } + } + } + + return true; + } + #endregion + + #region Utility Functions + /// + /// Compares a string locale to a WIN32 localeId + /// + public static bool CompareLocales(int localeId, string locale, bool ignoreRegion) + { + // parse locale. + CultureInfo culture; + try + { + culture = new CultureInfo(locale); + } + catch (Exception) + { + return false; + } + + // only match the language portion of the locale id. + if (ignoreRegion) + { + if ((localeId & culture.LCID & 0x3FF) == (localeId & 0x3FF)) + { + return true; + } + } + + // check for exact match. + else + { + if (localeId == culture.LCID) + { + return true; + } + } + + return false; + } + + /// + /// Reports an unexpected exception during a COM operation. + /// + public static void TraceComError(Exception e, string format, params object[] args) + { + var message = Utils.Format(format, args); + + var code = Marshal.GetHRForException(e); + + var error = code.ToString(); + + if (error == null) + { + Utils.Trace(e, message); + return; + } + + Utils.Trace(e, "{0:X}: {1}", error, message); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Server.cs b/Technosoftware/DaAeHdaClient.Com/Server.cs new file mode 100644 index 0000000..e9b41c0 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Server.cs @@ -0,0 +1,599 @@ +#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; +using Technosoftware.DaAeHdaClient.Com.Utilities; +using Technosoftware.DaAeHdaClient.Utilities; +using Technosoftware.OpcRcw.Comn; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// An in-process wrapper for a remote OPC COM server (not thread safe). + /// + internal class Server : IOpcServer + { + #region Fields + + /// + /// The COM server wrapped by the object. + /// + protected object server_; + + /// + /// The URL containing host, prog id and clsid information for The remote server. + /// + protected OpcUrl url_; + + /// + /// A connect point with the COM server. + /// + private ConnectionPoint connection_; + + /// + /// The internal object that implements the IOPCShutdown interface. + /// + private Callback callback_; + + /// + /// The synchronization object for server access + /// + private static volatile object lock_ = new object(); + + private int outstandingCalls_; + + #endregion + + #region Constructors + /// + /// Initializes the object. + /// + internal Server() + { + url_ = null; + server_ = null; + callback_ = new Callback(this); + } + + /// + /// Initializes the object with the specifed COM server. + /// + internal Server(OpcUrl url, object server) + { + if (url == null) throw new ArgumentNullException(nameof(url)); + + url_ = (OpcUrl)url.Clone(); + server_ = server; + callback_ = new Callback(this); + } + #endregion + + #region IDisposable Members + /// + /// The finalizer. + /// + ~Server() + { + Dispose(false); + } + + /// + /// Releases unmanaged resources held by the object. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!disposed_) + { + lock (this) + { + if (disposing) + { + // Free other state (managed objects). + + // close callback connections. + if (connection_ != null) + { + connection_.Dispose(); + connection_ = null; + } + } + + DisableDCOMCallCancellation(); + + // Free your own state (unmanaged objects). + // Set large fields to null. + + // release server. + Interop.ReleaseServer(server_); + server_ = null; + } + + disposed_ = true; + } + } + + private bool disposed_; + #endregion + + #region Public Methods + + /// + /// Connects to the server with the specified URL and credentials. + /// + public virtual void Initialize(OpcUrl url, OpcConnectData connectData) + { + if (url == null) throw new ArgumentNullException(nameof(url)); + + lock (lock_) + { + // re-connect only if the url has changed or has not been initialized. + if (url_ == null || !url_.Equals(url)) + { + // release the current server. + if (server_ != null) + { + Uninitialize(); + } + + // instantiate a new server. + server_ = (IOPCCommon)Factory.Connect(url, connectData); + } + + // save url. + url_ = (OpcUrl)url.Clone(); + } + } + + /// + /// Releases The remote server. + /// + public virtual void Uninitialize() + { + lock (lock_) + { + Dispose(); + } + } + + /// + /// Allows the client to optionally register a client name with the server. This is included primarily for debugging purposes. The recommended behavior is that the client set his Node name and EXE name here. + /// + public virtual void SetClientName(string clientName) + { + try + { + ((IOPCCommon)server_).SetClientName(clientName); + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCCommon.SetClientName", e); + } + } + + /// + /// Allows cancellation control of DCOM callbacks to the server - by default DCOM calls will wait the default DCOM timeout + /// to fail - this method allows for tigher control of the timeout to wait. Note that DOCM calls can only be controlled + /// on a COM Single Threaded Apartment thread - use [STAThread] attribute on your application entry point or use Thread SetThreadApartment + /// before the thread the server is operating on is created to STA. + /// + /// The DCOM call timeout - uses the default timeout if not specified + public void EnableDCOMCallCancellation(TimeSpan timeout = default) + { + DCOMCallWatchdog.Enable(timeout); + } + + /// + /// Disables cancellation control of DCOM calls to the server + /// + public void DisableDCOMCallCancellation() + { + DCOMCallWatchdog.Disable(); + } + + #endregion + + #region IOpcServer Members + + /// + /// An event to receive server shutdown notifications. + /// + public virtual event OpcServerShutdownEventHandler ServerShutdownEvent + { + add + { + lock (lock_) + { + try + { + Advise(); + callback_.ServerShutdown += value; + } + catch + { + // shutdown not supported. + } + } + } + + remove + { + lock (lock_) + { + callback_.ServerShutdown -= value; + Unadvise(); + } + } + } + + /// + /// The locale used in any error messages or results returned to the client. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + public virtual string GetLocale() + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + try + { + ((IOPCCommon)server_).GetLocaleID(out var localeId); + return Interop.GetLocale(localeId); + } + catch (Exception e) + { + throw Interop.CreateException("IOPCCommon.GetLocaleID", e); + } + } + } + + /// + /// Sets the locale used in any error messages or results returned to the client. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + /// A locale that the server supports and is the best match for the requested locale. + public virtual string SetLocale(string locale) + { + lock (this) + { + if (server_ == null) throw new NotConnectedException(); + + var lcid = Interop.GetLocale(locale); + + try + { + ((IOPCCommon)server_).SetLocaleID(lcid); + } + catch (Exception e) + { + if (lcid != 0) + { + throw Interop.CreateException("IOPCCommon.SetLocaleID", e); + } + + // use LOCALE_SYSTEM_DEFAULT if the server does not support the Neutral LCID. + try { ((IOPCCommon)server_).SetLocaleID(0x800); } + catch { } + } + + return GetLocale(); + } + } + + /// + /// Returns the locales supported by the server + /// + /// The first element in the array must be the default locale for the server. + /// An array of locales with the format "[languagecode]-[country/regioncode]". + public virtual string[] GetSupportedLocales() + { + lock (lock_) + { + if (server_ == null) throw new NotConnectedException(); + + try + { + var count = 0; + var pLocaleIDs = IntPtr.Zero; + + ((IOPCCommon)server_).QueryAvailableLocaleIDs(out count, out pLocaleIDs); + + var localeIDs = Interop.GetInt32s(ref pLocaleIDs, count, true); + + if (localeIDs != null) + { + var locales = new ArrayList(); + + foreach (var localeID in localeIDs) + { + try { locales.Add(Interop.GetLocale(localeID)); } + catch { } + } + + return (string[])locales.ToArray(typeof(string)); + } + + return null; + } + catch + { + //throw Interop.CreateException("IOPCCommon.QueryAvailableLocaleIDs", e); + return null; + } + } + } + + /// + /// Returns the localized text for the specified result code. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + /// The result code identifier. + /// A message localized for the best match for the requested locale. + public virtual string GetErrorText(string locale, OpcResult resultId) + { + lock (lock_) + { + if (server_ == null) throw new NotConnectedException(); + + try + { + var currentLocale = GetLocale(); + + if (currentLocale != locale) + { + SetLocale(locale); + } + + ((IOPCCommon)server_).GetErrorString(resultId.Code, out var errorText); + + if (currentLocale != locale) + { + SetLocale(currentLocale); + } + + return errorText; + } + catch (Exception e) + { + throw Utilities.Interop.CreateException("IOPCServer.GetErrorString", e); + } + } + } + #endregion + + #region Protected Members + /// + /// Releases all references to the server. + /// + protected virtual void ReleaseServer() + { + lock (lock_) + { + SafeNativeMethods.ReleaseServer(server_); + server_ = null; + } + } + + /// + /// Checks if the server supports the specified interface. + /// + /// The interface to check. + /// True if the server supports the interface. + protected bool SupportsInterface() where T : class + { + lock (lock_) + { + return server_ is T; + } + } + #endregion + + #region COM Call Tracing + /// + /// Must be called before any COM call. + /// + /// The interface to used when making the call. + /// Name of the method. + /// if set to true interface is an required interface and and exception is thrown on error. + /// + protected T BeginComCall(string methodName, bool isRequiredInterface) where T : class + { + return BeginComCall(server_, methodName, isRequiredInterface); + } + + /// + /// Must be called before any COM call. + /// + /// The interface to used when making the call. + /// Parent COM object + /// Name of the method. + /// if set to true interface is an required interface and and exception is thrown on error. + /// + protected T BeginComCall(object parent, string methodName, bool isRequiredInterface) where T : class + { + Utils.Trace(Utils.TraceMasks.ExternalSystem, "{0} called.", methodName); + + lock (lock_) + { + outstandingCalls_++; + + if (parent == null) + { + if (isRequiredInterface) + { + throw new NotConnectedException(); + } + } + + var comObject = parent as T; + + if (comObject == null) + { + if (isRequiredInterface) + { + throw new NotSupportedException(Utils.Format("OPC Interface '{0}' is a required interface but not supported by the server.", typeof(T).Name)); + } + else + { + Utils.Trace(Utils.TraceMasks.ExternalSystem, "OPC Interface '{0}' is not supported by server but it is only an optional one.", typeof(T).Name); + } + } + + DCOMCallWatchdog.Set(); + + return comObject; + } + } + + /// + /// Must called if a COM call returns an unexpected exception. + /// + /// Name of the method. + /// The exception. + /// Note that some COM calls are expected to return errors. + protected void ComCallError(string methodName, Exception e) + { + SafeNativeMethods.TraceComError(e, methodName); + } + + /// + /// Must be called in the finally block after making a COM call. + /// + /// Name of the method. + protected void EndComCall(string methodName) + { + Utils.Trace(Utils.TraceMasks.ExternalSystem, "{0} completed.", methodName); + + lock (lock_) + { + outstandingCalls_--; + + DCOMCallWatchdog.Reset(); + } + } + #endregion + + #region Private Methods + + /// + /// Establishes a connection point callback with the COM server. + /// + private void Advise() + { + if (connection_ == null) + { + connection_ = new ConnectionPoint(server_, typeof(IOPCShutdown).GUID); + connection_.Advise(callback_); + } + } + + /// + /// Closes a connection point callback with the COM server. + /// + private void Unadvise() + { + if (connection_ != null) + { + if (connection_.Unadvise() == 0) + { + connection_.Dispose(); + connection_ = null; + } + } + } + + /// + /// A class that implements the IOPCShutdown interface. + /// + private class Callback : IOPCShutdown + { + /// + /// Initializes the object with the containing subscription object. + /// + public Callback(Server server) + { + m_server = server; + } + + /// + /// An event to receive server shutdown notificiations. + /// + public event OpcServerShutdownEventHandler ServerShutdown + { + add { lock (lock_) { m_serverShutdown += value; } } + remove { lock (lock_) { m_serverShutdown -= value; } } + } + + /// + /// A table of item identifiers indexed by internal handle. + /// + private Server m_server = null; + + /// + /// Raised when data changed callbacks arrive. + /// + private event OpcServerShutdownEventHandler m_serverShutdown = null; + + /// + /// Called when a shutdown event is received. + /// + public void ShutdownRequest(string reason) + { + try + { + lock (lock_) + { + if (m_serverShutdown != null) + { + m_serverShutdown(reason); + } + } + } + catch (Exception e) + { + var stack = e.StackTrace; + } + } + } + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/ServerEnumerator.cs b/Technosoftware/DaAeHdaClient.Com/ServerEnumerator.cs new file mode 100644 index 0000000..3027f7a --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/ServerEnumerator.cs @@ -0,0 +1,320 @@ +#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.Net; +using System.Collections; +using System.Runtime.InteropServices; + +using Technosoftware.OpcRcw.Comn; +using Technosoftware.DaAeHdaClient.Ae; +using Technosoftware.DaAeHdaClient.Da; +using Technosoftware.DaAeHdaClient.Hda; +#endregion + +namespace Technosoftware.DaAeHdaClient.Com +{ + /// + /// A unique identifier for the result of an operation of an item. + /// + public class ServerEnumerator : IOpcDiscovery + { + //====================================================================== + // IDisposable + + /// + /// Frees all unmanaged resources + /// + public void Dispose() {} + + //====================================================================== + // IDiscovery + + /// + /// Enumerates hosts that may be accessed for server discovery. + /// + public string[] EnumerateHosts() + { + return Interop.EnumComputers(); + } + + /// + /// Returns a list of servers that support the specified interface specification. + /// + public OpcServer[] GetAvailableServers(OpcSpecification specification) + { + return GetAvailableServers(specification, null, null); + } + + /// + /// Returns a list of servers that support the specified specification on the specified host. + /// + public OpcServer[] GetAvailableServers(OpcSpecification specification, string host, OpcConnectData connectData) + { + lock (this) + { + var credentials = (connectData != null)?connectData.GetCredential(null, null):null; + + // connect to the server. + m_server = (IOPCServerList2)Interop.CreateInstance(CLSID, host, credentials, connectData?.UseConnectSecurity ?? false); + m_host = host; + + try + { + var servers = new ArrayList(); + + // convert the interface version to a guid. + var catid = new Guid(specification.Id); + + // get list of servers in the specified specification. + IOPCEnumGUID enumerator = null; + + m_server.EnumClassesOfCategories( + 1, + new Guid[] { catid }, + 0, + null, + out enumerator); + + // read clsids. + var clsids = ReadClasses(enumerator); + + // release enumerator object. + Interop.ReleaseServer(enumerator); + enumerator = null; + + // fetch class descriptions. + foreach (var clsid in clsids) + { + var factory = new Factory(); + + try + { + var url = CreateUrl(specification, clsid); + + OpcServer server = null; + + if (specification == OpcSpecification.OPC_DA_30) + { + server = new TsCDaServer(factory, url); + } + + else if (specification == OpcSpecification.OPC_DA_20) + { + server = new TsCDaServer(factory, url); + } + + else if (specification == OpcSpecification.OPC_AE_10) + { + server = new TsCAeServer(factory, url); + } + + else if (specification == OpcSpecification.OPC_HDA_10) + { + server = new TsCHdaServer(factory, url); + } + + servers.Add(server); + } + catch (Exception) + { + // ignore bad clsids. + } + } + + return (OpcServer[])servers.ToArray(typeof(OpcServer)); + } + finally + { + // free the server. + Interop.ReleaseServer(m_server); + m_server = null; + } + } + } + + /// + /// Looks up the CLSID for the specified prog id on a remote host. + /// + public Guid CLSIDFromProgID(string progID, string host, OpcConnectData connectData) + { + lock (this) + { + var credentials = (connectData != null)?connectData.GetCredential(null, null):null; + + // connect to the server. + m_server = (IOPCServerList2)Interop.CreateInstance(CLSID, host, credentials, connectData?.UseConnectSecurity ?? false); + m_host = host; + + // lookup prog id. + Guid clsid; + + try + { + m_server.CLSIDFromProgID(progID, out clsid); + } + catch + { + clsid = Guid.Empty; + } + finally + { + Interop.ReleaseServer(m_server); + m_server = null; + } + + // return empty guid if prog id not found. + return clsid; + } + } + + //====================================================================== + // Private Members + + /// + /// The server enumerator COM server. + /// + private IOPCServerList2 m_server = null; + + /// + /// The host where the servers are being enumerated. + /// + private string m_host = null; + + /// + /// The ProgID for the OPC Server Enumerator. + /// + private const string ProgID = "OPC.ServerList.1"; + + /// + /// The CLSID for the OPC Server Enumerator. + /// + private static readonly Guid CLSID = new Guid("13486D51-4821-11D2-A494-3CB306C10000"); + + //====================================================================== + // Private Methods + + /// + /// Reads the guids from the enumerator. + /// + private Guid[] ReadClasses(IOPCEnumGUID enumerator) + { + var guids = new ArrayList(); + var count = 10; + + // create buffer. + var buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Guid))*count); + + try + { + + int fetched; + do + { + try + { + enumerator.Next(count, buffer, out fetched); + + var pPos = buffer; + + for (var ii = 0; ii < fetched; ii++) + { + var guid = (Guid)Marshal.PtrToStructure(pPos, typeof(Guid)); + guids.Add(guid); + pPos = (IntPtr)(pPos.ToInt64() + Marshal.SizeOf(typeof(Guid))); + } + } + catch + { + break; + } + } + while (fetched > 0); + + return (Guid[])guids.ToArray(typeof(Guid)); + } + finally + { + Marshal.FreeCoTaskMem(buffer); + } + } + + /// + /// Reads the server details from the enumerator. + /// + OpcUrl CreateUrl(OpcSpecification specification, Guid clsid) + { + // initialize the server url. + var url = new OpcUrl(); + + url.HostName = m_host; + url.Port = 0; + url.Path = null; + + if (specification == OpcSpecification.OPC_DA_30) { url.Scheme = OpcUrlScheme.DA; } + else if (specification == OpcSpecification.OPC_DA_20) { url.Scheme = OpcUrlScheme.DA; } + else if (specification == OpcSpecification.OPC_DA_10) { url.Scheme = OpcUrlScheme.DA; } + else if (specification == OpcSpecification.OPC_AE_10) { url.Scheme = OpcUrlScheme.AE; } + else if (specification == OpcSpecification.OPC_HDA_10) { url.Scheme = OpcUrlScheme.HDA; } + + try + { + // fetch class details from the enumerator. + string progID = null; + string description = null; + string verIndProgID = null; + + m_server.GetClassDetails( + ref clsid, + out progID, + out description, + out verIndProgID); + + // create the server URL path. + if (verIndProgID != null) + { + url.Path = string.Format("{0}/{1}", verIndProgID, "{" + clsid.ToString() + "}"); + } + else if (progID != null) + { + url.Path = string.Format("{0}/{1}", progID, "{" + clsid.ToString() + "}"); + } + } + catch (Exception) + { + // bad value in registry. + } + finally + { + // default to the clsid if the prog is not known. + if (url.Path == null) + { + url.Path = string.Format("{0}", "{" + clsid.ToString() + "}"); + } + } + + // return the server url. + return url; + } + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Technosoftware.DaAeHdaClient.Com.csproj b/Technosoftware/DaAeHdaClient.Com/Technosoftware.DaAeHdaClient.Com.csproj new file mode 100644 index 0000000..3f2a46e --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Technosoftware.DaAeHdaClient.Com.csproj @@ -0,0 +1,21 @@ + + + + Technosoftware.DaAeHdaClient.Com + net6.0 + 9.0 + Technosoftware.DaAeHdaSolution.DaAeHdaClient.Com + OPC DA/AE/HDA Client Solution .NET + AnyCPU + + + + + + + + + + + + diff --git a/Technosoftware/DaAeHdaClient.Com/Utilities/DCOMCallWatchdog.cs b/Technosoftware/DaAeHdaClient.Com/Utilities/DCOMCallWatchdog.cs new file mode 100644 index 0000000..262b131 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Utilities/DCOMCallWatchdog.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Technosoftware.DaAeHdaClient.Utilities; + +namespace Technosoftware.DaAeHdaClient.Com.Utilities +{ + /// + /// The result of DCOM watchdog + /// + public enum DCOMWatchdogResult + { + /// + /// Watchdog has not been set/there is no result + /// + None = 0, + /// + /// The Set/Reset cycle was manually completed i.e. the DCOM call did not timeout + /// + Completed, + /// + /// No Reset call occurred with the timeout period thus the current DCOM call was automatically cancelled + /// + TimedOut, + /// + /// The current DCOM call was manually cancelled + /// + ManuallyCancelled + } + + /// + /// Watchdog mechanism to allow for cancellation of DCOM calls. Note that this mechanism will only work for a STA thread apartment - the thread on which + /// the watchdog is Set and DCOM calls are made have to be the same thread and the thread apartment model has to be set to STA. + /// + public static class DCOMCallWatchdog + { + #region Fields + private const int DEFAULT_TIMEOUT_SECONDS = 10; + + private static object watchdogLock_ = new object(); + private static uint watchDogThreadID_; + private static bool isCancelled_; + private static TimeSpan timeout_ = TimeSpan.Zero; //disabled by default + private static Task watchdogTask_; + private static DCOMWatchdogResult lastWatchdogResult_ = DCOMWatchdogResult.None; + private static DateTime setStart_; + + #endregion + + #region Properties + + /// + /// The result of the last watchdog set/reset operation + /// + public static DCOMWatchdogResult LastWatchdogResult + { + get { return lastWatchdogResult_; } + } + + /// + /// The current native thread ID on which the watchdog has been enabled + /// + public static uint WatchDogThreadID + { + get => watchDogThreadID_; + } + + /// + /// Indicates if the watchdog mechanism is active or not + /// + public static bool IsEnabled + { + get => timeout_ != TimeSpan.Zero; + } + + /// + /// Indicates if the watchdog has been set and is busy waiting for a call completion Reset to be called or a timeout to occur. + /// + public static bool IsSet + { + get => WatchDogThreadID != 0; + } + + /// + /// Indicates if the watchdog was cancelled due to a timeout + /// + public static bool IsCancelled + { + get => isCancelled_; + } + + /// + /// The watchdog timeout timespan + /// + public static TimeSpan Timeout + { + get => timeout_; + set + { + Enable(value); + } + } + + #endregion + + #region Methods + + /// + /// Enables the Watchdog mechanism. This can be called from any thread and does not have to be the DCOM call originator thread. + /// Uses the default call timeout. + /// + public static void Enable() + { + Enable(TimeSpan.FromSeconds(DEFAULT_TIMEOUT_SECONDS)); + } + + + /// + /// Enables the Watchdog mechanism. This can be called from any thread and does not have to be the DCOM call originator thread. + /// + /// The maximum time to wait for a DCOM call to succeed before it is cancelled. Note that DCOM will typically timeout + /// between 1-2 minutes, depending on the OS + public static void Enable(TimeSpan timeout) + { + if (timeout == TimeSpan.Zero) + { + timeout = TimeSpan.FromSeconds(DEFAULT_TIMEOUT_SECONDS); + } + + lock (watchdogLock_) + { + timeout_ = timeout; + } + + watchdogTask_ = Task.Run(() => WatchdogTask()); + } + + /// + /// Disables the watchdog mechanism and stops any call cancellations. + /// + /// True if enabled and now disabled, otherwise false + + public static bool Disable() + { + lock (watchdogLock_) + { + if (IsEnabled) + { + timeout_ = TimeSpan.Zero; + + return true; + } + else + { + return false; + } + } + } + + /// + /// Sets the watchdog timer active on the current thread. If Reset is not called within the timeout period, any current thread DCOM call will be cancelled. The + /// calling thread must be the originator of the DCOM call and must be an STA thread. + /// + /// True if the watchdog set succeeds or was already set for the current thread, else false if the watchdog is not enabled. + public static bool Set() + { + if (IsEnabled) + { + var apartmentState = Thread.CurrentThread.GetApartmentState(); + + if (apartmentState != ApartmentState.STA) + { + throw new InvalidOperationException("COM calls can only be cancelled on a COM STA apartment thread - use [STAThread] attibute or set the state of the thread on creation"); + } + + lock (watchdogLock_) + { + var threadId = Interop.GetCurrentThreadId(); + + if (IsSet) + { + if (threadId != watchDogThreadID_) + { + throw new InvalidOperationException($"Attempt to set call cancellation on different thread [{threadId}] to where it was already enabled [{watchDogThreadID_}]"); + } + } + else + { + isCancelled_ = false; + watchDogThreadID_ = 0; + lastWatchdogResult_ = DCOMWatchdogResult.None; + + //enable DCOM call cancellation for duration of the watchdog + var hresult = Interop.CoEnableCallCancellation(IntPtr.Zero); + + if (hresult == 0) + { + setStart_ = DateTime.UtcNow; + watchDogThreadID_ = threadId; + + Utils.Trace(Utils.TraceMasks.Information, $"COM call cancellation on thread [{watchDogThreadID_}] was set with timeout [{timeout_.TotalSeconds} seconds]"); + } + else + { + throw new Exception($"Failed to set COM call cancellation (HResult = {hresult})"); + } + } + } + + return true; + } + else + { + return false; + } + } + + /// + /// Refreshes the watchdog activity timer to now, effectively resetting the time to wait. + /// + /// True if the watchdog time was updated, else False if the watchdog timer is not Enabled or Set + public static bool Update() + { + if (IsEnabled) + { + lock (watchdogLock_) + { + if (IsSet) + { + setStart_ = DateTime.UtcNow; + + return true; + } + else + { + return false; + } + } + } + else + { + return false; + } + } + + /// + /// Resets the watchdog timer for the current thread. This should be called after a DCOM call returns to indicate the call succeeded, and thus cancelling the + /// watchdog timer. + /// + /// True if the watchdog timer was reset for the current thread, else False if the timer was not set for the thread of the watchdog is not enabled. + public static bool Reset() + { + if (IsEnabled) + { + lock (watchdogLock_) + { + if (IsSet) + { + var threadId = Interop.GetCurrentThreadId(); + + if (threadId == watchDogThreadID_) + { + if (!IsCancelled) + { + lastWatchdogResult_ = DCOMWatchdogResult.Completed; + } + + watchDogThreadID_ = 0; + isCancelled_ = false; + + //disable DCOM call cancellation + var hresult = Interop.CoDisableCallCancellation(IntPtr.Zero); + + Utils.Trace(Utils.TraceMasks.Information, $"COM call cancellation on thread [{watchDogThreadID_}] was reset [HRESULT = {hresult}]"); + } + else + { + throw new Exception($"COM call cancellation cannot be reset from different thread [{threadId}] it was set on [{watchDogThreadID_}]"); + } + } + } + + return true; + } + else + { + return false; + } + } + + /// + /// Allows for manual cancellation of the current DCOM call + /// + /// + public static bool Cancel() + { + return Cancel(DCOMWatchdogResult.ManuallyCancelled); + } + + /// + /// Cancels the current DCOM call if there is one active + /// + /// + /// The reason for the cancellation + private static bool Cancel(DCOMWatchdogResult reason) + { + if (IsEnabled) + { + lock (watchdogLock_) + { + if (!IsCancelled && IsSet) + { + isCancelled_ = true; + + //cancel the current DCOM call immediately + var hresult = Interop.CoCancelCall(watchDogThreadID_, 0); + + Utils.Trace(Utils.TraceMasks.Information, $"COM call on thread [{watchDogThreadID_}] was cancelled [HRESULT = {hresult}]"); + + lastWatchdogResult_ = reason; + + return true; + } + else + { + return false; + } + } + } + else + { + return false; + } + } + + /// + /// The Watchdog Task is a seperate thread that is activated when the Watchdog is enabled. It checks the time since the last Set was called and + /// then cancels the current DCOM call automatically if Reset is not called within the timeout period. + /// + private static void WatchdogTask() + { + while (IsEnabled) + { + try + { + if (IsSet & !IsCancelled) + { + if (TimeElapsed(setStart_) >= timeout_) + { + Utils.Trace(Utils.TraceMasks.Information, $"Sync call watchdog for thread [{watchDogThreadID_}] timed out - cancelling current call..."); + + Cancel(DCOMWatchdogResult.TimedOut); + } + } + } + catch (Exception e) + { + Utils.Trace(Utils.TraceMasks.Error, $"Error in Sync call watchdog thread : {e.ToString()}"); + } + finally + { + Thread.Sleep(1); + } + } + } + + + private static TimeSpan TimeElapsed(DateTime startTime) + { + var now = DateTime.UtcNow; + + startTime = startTime.ToUniversalTime(); + + if (startTime > now) + { + return startTime - now; + } + else + { + return now - startTime; + } + } + + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient.Com/Utilities/Interop.cs b/Technosoftware/DaAeHdaClient.Com/Utilities/Interop.cs new file mode 100644 index 0000000..4d72c04 --- /dev/null +++ b/Technosoftware/DaAeHdaClient.Com/Utilities/Interop.cs @@ -0,0 +1,1969 @@ +#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.Net; +using System.Globalization; +using System.Runtime.InteropServices; + +using Technosoftware.DaAeHdaClient.Da; +#pragma warning disable 618 +#endregion + +namespace Technosoftware.DaAeHdaClient.Com.Utilities +{ + /// + /// Exposes WIN32 and COM API functions. + /// + public class Interop + { + #region NetApi Function Declarations + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct SERVER_INFO_100 + { + public readonly uint sv100_platform_id; + [MarshalAs(UnmanagedType.LPWStr)] public readonly string sv100_name; + } + + private const uint LEVEL_SERVER_INFO_100 = 100; + private const uint LEVEL_SERVER_INFO_101 = 101; + + private const int MAX_PREFERRED_LENGTH = -1; + + private const uint SV_TYPE_WORKSTATION = 0x00000001; + private const uint SV_TYPE_SERVER = 0x00000002; + + [DllImport("Netapi32.dll")] + private static extern int NetServerEnum( + IntPtr servername, + uint level, + out IntPtr bufptr, + int prefmaxlen, + out int entriesread, + out int totalentries, + uint servertype, + IntPtr domain, + IntPtr resume_handle); + + [DllImport("Netapi32.dll")] + private static extern int NetApiBufferFree(IntPtr buffer); + + /// + /// Enumerates computers on the local network. + /// + public static string[] EnumComputers() + { + IntPtr pInfo; + int entriesRead; + int totalEntries; + var result = NetServerEnum( + IntPtr.Zero, + LEVEL_SERVER_INFO_100, + out pInfo, + MAX_PREFERRED_LENGTH, + out entriesRead, + out totalEntries, + SV_TYPE_WORKSTATION | SV_TYPE_SERVER, + IntPtr.Zero, + IntPtr.Zero); + + if (result != 0) + { + throw new ApplicationException("NetApi Error = " + string.Format("0x{0:X8}", result)); + } + + var computers = new string[entriesRead]; + + var pos = pInfo; + + for (var ii = 0; ii < entriesRead; ii++) + { + var info = (SERVER_INFO_100)Marshal.PtrToStructure(pos, typeof(SERVER_INFO_100)); + + computers[ii] = info.sv100_name; + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(SERVER_INFO_100))); + } + + NetApiBufferFree(pInfo); + + return computers; + } + #endregion + + #region OLE32 Function/Interface Declarations + private const int MAX_MESSAGE_LENGTH = 1024; + + private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + /// + /// The WIN32 system default locale. + /// + public const int LOCALE_SYSTEM_DEFAULT = 0x800; + + /// + /// The WIN32 user default locale. + /// + public const int LOCALE_USER_DEFAULT = 0x400; + + /// + /// The size, in bytes, of a VARIANT structure. + /// + private const int VARIANT_SIZE = 0x10; + + private const int DISP_E_TYPEMISMATCH = -0x7FFDFFFB; // 0x80020005 + private const int DISP_E_OVERFLOW = -0x7FFDFFF6; // 0x8002000A + + private const int VARIANT_NOVALUEPROP = 0x01; + + private const int VARIANT_ALPHABOOL = 0x02; + // For VT_BOOL to VT_BSTR conversions convert to "True"/"False" instead of + + private const uint RPC_C_AUTHN_NONE = 0; + private const uint RPC_C_AUTHN_DCE_PRIVATE = 1; + private const uint RPC_C_AUTHN_DCE_PUBLIC = 2; + private const uint RPC_C_AUTHN_DEC_PUBLIC = 4; + private const uint RPC_C_AUTHN_GSS_NEGOTIATE = 9; + private const uint RPC_C_AUTHN_WINNT = 10; + private const uint RPC_C_AUTHN_GSS_SCHANNEL = 14; + private const uint RPC_C_AUTHN_GSS_KERBEROS = 16; + private const uint RPC_C_AUTHN_DPA = 17; + private const uint RPC_C_AUTHN_MSN = 18; + private const uint RPC_C_AUTHN_DIGEST = 21; + private const uint RPC_C_AUTHN_MQ = 100; + private const uint RPC_C_AUTHN_DEFAULT = 0xFFFFFFFF; + + private const uint RPC_C_AUTHZ_NONE = 0; + private const uint RPC_C_AUTHZ_NAME = 1; + private const uint RPC_C_AUTHZ_DCE = 2; + private const uint RPC_C_AUTHZ_DEFAULT = 0xffffffff; + + private const uint RPC_C_AUTHN_LEVEL_DEFAULT = 0; + private const uint RPC_C_AUTHN_LEVEL_NONE = 1; + private const uint RPC_C_AUTHN_LEVEL_CONNECT = 2; + private const uint RPC_C_AUTHN_LEVEL_CALL = 3; + private const uint RPC_C_AUTHN_LEVEL_PKT = 4; + private const uint RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5; + private const uint RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; + + private const uint RPC_C_IMP_LEVEL_ANONYMOUS = 1; + private const uint RPC_C_IMP_LEVEL_IDENTIFY = 2; + private const uint RPC_C_IMP_LEVEL_IMPERSONATE = 3; + private const uint RPC_C_IMP_LEVEL_DELEGATE = 4; + + private const uint EOAC_NONE = 0x00; + private const uint EOAC_MUTUAL_AUTH = 0x01; + private const uint EOAC_CLOAKING = 0x10; + private const uint EOAC_STATIC_CLOAKING = 0x20; + private const uint EOAC_DYNAMIC_CLOAKING = 0x40; + private const uint EOAC_SECURE_REFS = 0x02; + private const uint EOAC_ACCESS_CONTROL = 0x04; + private const uint EOAC_APPID = 0x08; + + private const uint CLSCTX_INPROC_SERVER = 0x1; + private const uint CLSCTX_INPROC_HANDLER = 0x2; + private const uint CLSCTX_LOCAL_SERVER = 0x4; + private const uint CLSCTX_REMOTE_SERVER = 0x10; + private const uint CLSCTX_DISABLE_AAA = 0x8000; + + private const uint SEC_WINNT_AUTH_IDENTITY_ANSI = 0x1; + private const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 0x2; + + private const int LOGON32_PROVIDER_DEFAULT = 0; + private const int LOGON32_LOGON_INTERACTIVE = 2; + private const int LOGON32_LOGON_NETWORK = 3; + + private const int SECURITY_ANONYMOUS = 0; + private const int SECURITY_IDENTIFICATION = 1; + private const int SECURITY_IMPERSONATION = 2; + private const int SECURITY_DELEGATION = 3; + + /// + /// The base for the WIN32 FILETIME structure. + /// + private static readonly DateTime FILETIME_BaseTime = new DateTime(1601, 1, 1); + + private static readonly IntPtr COLE_DEFAULT_PRINCIPAL = new IntPtr(-1); + private static readonly IntPtr COLE_DEFAULT_AUTHINFO = new IntPtr(-1); + private static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); + + [DllImport("Kernel32.dll")] + private static extern int FormatMessageW( + int dwFlags, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + IntPtr lpBuffer, + int nSize, + IntPtr Arguments); + + [DllImport("Kernel32.dll")] + private static extern int GetSystemDefaultLangID(); + + [DllImport("Kernel32.dll")] + private static extern int GetUserDefaultLangID(); + + [DllImport("OleAut32.dll")] + private static extern int VariantChangeTypeEx( + IntPtr pvargDest, + IntPtr pvarSrc, + int lcid, + ushort wFlags, + short vt); + + /// + /// Intializes a pointer to a VARIANT. + /// + [DllImport("oleaut32.dll")] + private static extern void VariantInit(IntPtr pVariant); + + /// + /// Frees all memory referenced by a VARIANT stored in unmanaged memory. + /// + [DllImport("oleaut32.dll")] + public static extern void VariantClear(IntPtr pVariant); + + [DllImport("ole32.dll")] + private static extern int CoInitializeSecurity( + IntPtr pSecDesc, + int cAuthSvc, + SOLE_AUTHENTICATION_SERVICE[] asAuthSvc, + IntPtr pReserved1, + uint dwAuthnLevel, + uint dwImpLevel, + IntPtr pAuthList, + uint dwCapabilities, + IntPtr pReserved3); + + [DllImport("ole32.dll")] + private static extern int CoQueryProxyBlanket( + [MarshalAs(UnmanagedType.IUnknown)] object pProxy, + ref uint pAuthnSvc, + ref uint pAuthzSvc, + [MarshalAs(UnmanagedType.LPWStr)] ref string pServerPrincName, + ref uint pAuthnLevel, + ref uint pImpLevel, + ref IntPtr pAuthInfo, + ref uint pCapabilities); + + [DllImport("ole32.dll")] + private static extern int CoSetProxyBlanket( + [MarshalAs(UnmanagedType.IUnknown)] object pProxy, + uint pAuthnSvc, + uint pAuthzSvc, + IntPtr pServerPrincName, + uint pAuthnLevel, + uint pImpLevel, + IntPtr pAuthInfo, + uint pCapabilities); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COSERVERINFO + { + public uint dwReserved1; + [MarshalAs(UnmanagedType.LPWStr)] + public string pwszName; + public IntPtr pAuthInfo; + public uint dwReserved2; + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COAUTHINFO + { + public uint dwAuthnSvc; + public uint dwAuthzSvc; + public IntPtr pwszServerPrincName; + public uint dwAuthnLevel; + public uint dwImpersonationLevel; + public IntPtr pAuthIdentityData; + public uint dwCapabilities; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct COAUTHIDENTITY + { + public IntPtr User; + public uint UserLength; + public IntPtr Domain; + public uint DomainLength; + public IntPtr Password; + public uint PasswordLength; + public uint Flags; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct MULTI_QI + { + public IntPtr iid; + [MarshalAs(UnmanagedType.IUnknown)] + public object pItf; + public uint hr; + } + + [DllImport("ole32.dll")] + private static extern void CoCreateInstanceEx( + ref Guid clsid, + [MarshalAs(UnmanagedType.IUnknown)] object punkOuter, + uint dwClsCtx, + [In] ref COSERVERINFO pServerInfo, + uint dwCount, + [In, Out] MULTI_QI[] pResults); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct LICINFO + { + public int cbLicInfo; + [MarshalAs(UnmanagedType.Bool)] + public bool fRuntimeKeyAvail; + [MarshalAs(UnmanagedType.Bool)] + public bool fLicVerified; + } + + [ComImport] + [GuidAttribute("00000001-0000-0000-C000-000000000046")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClassFactory + { + void CreateInstance( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.Interface)] + [Out] out object ppvObject); + + void LockServer( + [MarshalAs(UnmanagedType.Bool)] + bool fLock); + } + + [ComImport] + [GuidAttribute("B196B28F-BAB4-101A-B69C-00AA00341D07")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + private interface IClassFactory2 + { + void CreateInstance( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.Interface)] + [Out] out object ppvObject); + + void LockServer( + [MarshalAs(UnmanagedType.Bool)] + bool fLock); + + void GetLicInfo( + [In, Out] ref LICINFO pLicInfo); + + void RequestLicKey( + int dwReserved, + [MarshalAs(UnmanagedType.BStr)] + string pbstrKey); + + void CreateInstanceLic( + [MarshalAs(UnmanagedType.IUnknown)] + object punkOuter, + [MarshalAs(UnmanagedType.IUnknown)] + object punkReserved, + [MarshalAs(UnmanagedType.LPStruct)] + Guid riid, + [MarshalAs(UnmanagedType.BStr)] + string bstrKey, + [MarshalAs(UnmanagedType.IUnknown)] + [Out] out object ppvObject); + } + + [DllImport("ole32.dll")] + private static extern void CoGetClassObject( + [MarshalAs(UnmanagedType.LPStruct)] Guid clsid, + uint dwClsContext, + [In] ref COSERVERINFO pServerInfo, + [MarshalAs(UnmanagedType.LPStruct)] Guid riid, + [MarshalAs(UnmanagedType.IUnknown)][Out] out object ppv); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool LogonUser( + string lpszUsername, + string lpszDomain, + string lpszPassword, + int dwLogonType, + int dwLogonProvider, + ref IntPtr phToken); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto)] + private extern static bool CloseHandle(IntPtr handle); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private extern static bool DuplicateToken( + IntPtr ExistingTokenHandle, + int SECURITY_IMPERSONATION_LEVEL, + ref IntPtr DuplicateTokenHandle); + + #region Nested type: GUID + + /// + /// WIN32 GUID struct declaration. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct GUID + { + public readonly int Data1; + public readonly short Data2; + public readonly short Data3; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public readonly byte[] Data4; + } + + #endregion + + #region Nested type: SOLE_AUTHENTICATION_SERVICE + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + private struct SOLE_AUTHENTICATION_SERVICE + { + public readonly uint dwAuthnSvc; + public readonly uint dwAuthzSvc; + [MarshalAs(UnmanagedType.LPWStr)] public readonly string pPrincipalName; + public readonly int hr; + } + + [DllImport("ole32.dll")] + internal static extern int CoCancelCall(uint threadId, uint timeout); + + [DllImport("ole32.dll")] + internal static extern int CoEnableCallCancellation(IntPtr reserved); + + [DllImport("ole32.dll")] + internal static extern int CoDisableCallCancellation(IntPtr reserved); + + [DllImport("Kernel32.dll")] + internal static extern uint GetCurrentThreadId(); + + #endregion + + #endregion + + private static bool preserveUtc_ = true; + + /// + /// This flag suppresses the conversion to local time done during marshalling. + /// + public static bool PreserveUtc + { + get + { + lock (typeof(Interop)) + { + return preserveUtc_; + } + } + set + { + lock (typeof(Interop)) + { + preserveUtc_ = value; + } + } + } + + #region ServerInfo Class + /// + /// A class used to allocate and deallocate the elements of a COSERVERINFO structure. + /// + class ServerInfo + { + #region Public Interface + /// + /// Allocates a COSERVERINFO structure. + /// + public COSERVERINFO Allocate(string hostName, OpcUserIdentity identity) + { + // initialize server info structure. + var serverInfo = new COSERVERINFO(); + + serverInfo.pwszName = hostName; + serverInfo.pAuthInfo = IntPtr.Zero; + serverInfo.dwReserved1 = 0; + serverInfo.dwReserved2 = 0; + + // no authentication for default identity + if (OpcUserIdentity.IsDefault(identity)) + { + return serverInfo; + } + + m_hUserName = GCHandle.Alloc(identity.Username, GCHandleType.Pinned); + m_hPassword = GCHandle.Alloc(identity.Password, GCHandleType.Pinned); + m_hDomain = GCHandle.Alloc(identity.Domain, GCHandleType.Pinned); + + m_hIdentity = new GCHandle(); + + // create identity structure. + var authIdentity = new COAUTHIDENTITY(); + + authIdentity.User = m_hUserName.AddrOfPinnedObject(); + authIdentity.UserLength = (uint)((identity.Username != null) ? identity.Username.Length : 0); + authIdentity.Password = m_hPassword.AddrOfPinnedObject(); + authIdentity.PasswordLength = (uint)((identity.Password != null) ? identity.Password.Length : 0); + authIdentity.Domain = m_hDomain.AddrOfPinnedObject(); + authIdentity.DomainLength = (uint)((identity.Domain != null) ? identity.Domain.Length : 0); + authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + m_hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); + + // create authorization info structure. + var authInfo = new COAUTHINFO(); + + authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; + authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; + authInfo.pwszServerPrincName = IntPtr.Zero; + authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; + authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + authInfo.pAuthIdentityData = m_hIdentity.AddrOfPinnedObject(); + authInfo.dwCapabilities = EOAC_NONE; // EOAC_DYNAMIC_CLOAKING; + + m_hAuthInfo = GCHandle.Alloc(authInfo, GCHandleType.Pinned); + + // update server info structure. + serverInfo.pAuthInfo = m_hAuthInfo.AddrOfPinnedObject(); + + return serverInfo; + } + + /// + /// Deallocated memory allocated when the COSERVERINFO structure was created. + /// + public void Deallocate() + { + if (m_hUserName.IsAllocated) m_hUserName.Free(); + if (m_hPassword.IsAllocated) m_hPassword.Free(); + if (m_hDomain.IsAllocated) m_hDomain.Free(); + if (m_hIdentity.IsAllocated) m_hIdentity.Free(); + if (m_hAuthInfo.IsAllocated) m_hAuthInfo.Free(); + } + #endregion + + #region Private Members + private GCHandle m_hUserName; + private GCHandle m_hPassword; + private GCHandle m_hDomain; + private GCHandle m_hIdentity; + private GCHandle m_hAuthInfo; + #endregion + } + #endregion + + #region Initialization Functions + /// + /// Initializes COM security. + /// + public static void InitializeSecurity() + { + var error = CoInitializeSecurity( + IntPtr.Zero, + -1, + null, + IntPtr.Zero, + RPC_C_AUTHN_LEVEL_CONNECT, + RPC_C_IMP_LEVEL_IMPERSONATE, + IntPtr.Zero, + EOAC_DYNAMIC_CLOAKING, + IntPtr.Zero); + + // this call will fail in the debugger if the + // 'Debug | Enable Visual Studio Hosting Process' + // option is checked in the project properties. + if (error != 0) + { + // throw new ExternalException("CoInitializeSecurity: " + GetSystemMessage(error), error); + } + } + + /// + /// Determines if the host is the local host. + /// + private static bool IsLocalHost(string hostName) + { + // lookup requested host. + var requestedHost = Dns.GetHostEntry(hostName); + + if (requestedHost == null || requestedHost.AddressList == null) + { + return true; + } + + // check for loopback. + for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) + { + var requestedIP = requestedHost.AddressList[ii]; + + if (requestedIP == null || requestedIP.Equals(IPAddress.Loopback)) + { + return true; + } + } + + // lookup local host. + var localHost = Dns.GetHostEntry(Dns.GetHostName()); + + if (localHost == null || localHost.AddressList == null) + { + return false; + } + + // check for localhost. + for (var ii = 0; ii < requestedHost.AddressList.Length; ii++) + { + var requestedIP = requestedHost.AddressList[ii]; + + for (var jj = 0; jj < localHost.AddressList.Length; jj++) + { + if (requestedIP.Equals(localHost.AddressList[jj])) + { + return true; + } + } + } + + // must be remote. + return false; + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstance(Guid clsid, string hostName, OpcUserIdentity identity) + { + return CreateInstance1(clsid, hostName, identity); + } + + /// + /// Creates an instance of a COM server. + /// + public static object CreateInstance1(Guid clsid, string hostName, OpcUserIdentity identity) + { + var serverInfo = new ServerInfo(); + var coserverInfo = serverInfo.Allocate(hostName, identity); + + var hIID = GCHandle.Alloc(IID_IUnknown, GCHandleType.Pinned); + + var results = new MULTI_QI[1]; + + results[0].iid = hIID.AddrOfPinnedObject(); + results[0].pItf = null; + results[0].hr = 0; + + try + { + // check whether connecting locally or remotely. + var clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; + + if (!string.IsNullOrEmpty(hostName) && hostName != "localhost") + { + clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + } + + // create an instance. + CoCreateInstanceEx( + ref clsid, + null, + clsctx, + ref coserverInfo, + 1, + results); + } + finally + { + if (hIID.IsAllocated) hIID.Free(); + serverInfo.Deallocate(); + } + + if (results[0].hr != 0) + { + return null; + } + + return results[0].pItf; + } + + // COM impersonation is a nice feature but variations between behavoirs on different + // windows platforms make it virtually impossible to support. This code is left here + // in case it becomes a critical requirement in the future. +#if COM_IMPERSONATION_SUPPORT + /// + /// Returns the WindowsIdentity associated with a UserIdentity. + /// + public static WindowsPrincipal GetPrincipalFromUserIdentity(UserIdentity user) + { + if (UserIdentity.IsDefault(user)) + { + return null; + } + + // validate the credentials. + IntPtr token = IntPtr.Zero; + + bool result = LogonUser( + user.Username, + user.Domain, + user.Password, + LOGON32_LOGON_NETWORK, + LOGON32_PROVIDER_DEFAULT, + ref token); + + if (!result) + { + throw ServiceResultException.Create( + StatusCodes.BadIdentityTokenRejected, + "Could not logon as user '{0}'. Reason: {1}.", + user.Username, + GetSystemMessage(Marshal.GetLastWin32Error(), LOCALE_SYSTEM_DEFAULT)); + } + + try + { + // create the windows identity. + WindowsIdentity identity = new WindowsIdentity(token); + + // validate the identity. + identity.Impersonate(); + + // return a principal. + return new WindowsPrincipal(identity); + } + finally + { + CloseHandle(token); + } + } + + /// + /// Sets the security settings for the proxy. + /// + public static void SetProxySecurity(object server, UserIdentity user) + { + // allocate the + GCHandle hUserName = GCHandle.Alloc(user.Username, GCHandleType.Pinned); + GCHandle hPassword = GCHandle.Alloc(user.Password, GCHandleType.Pinned); + GCHandle hDomain = GCHandle.Alloc(user.Domain, GCHandleType.Pinned); + + GCHandle hIdentity = new GCHandle(); + + // create identity structure. + COAUTHIDENTITY authIdentity = new COAUTHIDENTITY(); + + authIdentity.User = hUserName.AddrOfPinnedObject(); + authIdentity.UserLength = (uint)((user.Username != null) ? user.Username.Length : 0); + authIdentity.Password = hPassword.AddrOfPinnedObject(); + authIdentity.PasswordLength = (uint)((user.Password != null) ? user.Password.Length : 0); + authIdentity.Domain = hDomain.AddrOfPinnedObject(); + authIdentity.DomainLength = (uint)((user.Domain != null) ? user.Domain.Length : 0); + authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + + hIdentity = GCHandle.Alloc(authIdentity, GCHandleType.Pinned); + + try + { + SetProxySecurity(server, hIdentity.AddrOfPinnedObject()); + } + finally + { + hUserName.Free(); + hPassword.Free(); + hDomain.Free(); + hIdentity.Free(); + } + } + + /// + /// Sets the security settings for the proxy. + /// + public static void SetProxySecurity(object server, IntPtr pAuthInfo) + { + // get the existing proxy settings. + uint pAuthnSvc = 0; + uint pAuthzSvc = 0; + string pServerPrincName = ""; + uint pAuthnLevel = 0; + uint pImpLevel = 0; + IntPtr pAuthInfo2 = IntPtr.Zero; + uint pCapabilities = 0; + + CoQueryProxyBlanket( + server, + ref pAuthnSvc, + ref pAuthzSvc, + ref pServerPrincName, + ref pAuthnLevel, + ref pImpLevel, + ref pAuthInfo2, + ref pCapabilities); + + pAuthnSvc = RPC_C_AUTHN_WINNT; + pAuthzSvc = RPC_C_AUTHZ_NONE; + pAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT; + pImpLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + pCapabilities = EOAC_DYNAMIC_CLOAKING; + + // update proxy security settings. + CoSetProxyBlanket( + server, + pAuthnSvc, + pAuthzSvc, + COLE_DEFAULT_PRINCIPAL, + pAuthnLevel, + pImpLevel, + pAuthInfo, + pCapabilities); + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstance2(Guid clsid, string hostName, UserIdentity identity) + { + // validate the host name before proceeding (exception thrown if host is not valid). + bool isLocalHost = IsLocalHost(hostName); + + // allocate the connection info. + ServerInfo serverInfo = new ServerInfo(); + COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); + object instance = null; + IClassFactory factory = null; + + try + { + // create the factory. + object server = null; + + CoGetClassObject( + clsid, + (isLocalHost)?CLSCTX_LOCAL_SERVER:CLSCTX_REMOTE_SERVER, + ref coserverInfo, + IID_IUnknown, + out server); + + // SetProxySecurity(server, coserverInfo.pAuthInfo); + + factory = (IClassFactory)server; + + // check for valid factory. + if (factory == null) + { + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory for COM server '{0}' on host '{1}'.", clsid, hostName); + } + + // SetProxySecurity(factory, coserverInfo.pAuthInfo); + + factory.CreateInstance(null, IID_IUnknown, out instance); + + // SetProxySecurity(instance, coserverInfo.pAuthInfo); + } + finally + { + serverInfo.Deallocate(); + } + + return instance; + } + + /// + /// Creates an instance of a COM server using the specified license key. + /// + public static object CreateInstanceWithLicenseKey(Guid clsid, string hostName, UserIdentity identity, string licenseKey) + { + ServerInfo serverInfo = new ServerInfo(); + COSERVERINFO coserverInfo = serverInfo.Allocate(hostName, identity); + object instance = null; + IClassFactory2 factory = null; + + try + { + // check whether connecting locally or remotely. + uint clsctx = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; + + if (hostName != null && hostName.Length > 0) + { + clsctx = CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + } + + // get the class factory. + object server = null; + + CoGetClassObject( + clsid, + clsctx, + ref coserverInfo, + typeof(IClassFactory2).GUID, + out server); + + // SetProxySecurity(server, coserverInfo.pAuthInfo); + + factory = (IClassFactory2)server; + + // check for valid factory. + if (factory == null) + { + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, "Could not load IClassFactory2 for COM server '{0}' on host '{1}'.", clsid, hostName); + } + + // SetProxySecurity(factory, coserverInfo.pAuthInfo); + + // create instance. + factory.CreateInstanceLic( + null, + null, + IID_IUnknown, + licenseKey, + out instance); + + // SetProxySecurity(instance, coserverInfo.pAuthInfo); + } + finally + { + serverInfo.Deallocate(); + ComUtils.ReleaseServer(factory); + } + + return instance; + } +#endif + #endregion + + + /// + /// Tests if the specified string matches the specified pattern. + /// + public static bool Match(string target, string pattern, bool caseSensitive) + { + // an empty pattern always matches. + if (pattern == null || pattern.Length == 0) + { + return true; + } + + // an empty string never matches. + if (target == null || target.Length == 0) + { + return false; + } + + // check for exact match + if (caseSensitive) + { + if (target == pattern) + { + return true; + } + } + else + { + if (target.ToLower() == pattern.ToLower()) + { + return true; + } + } + + char c; + char p; + char l; + + var pIndex = 0; + var tIndex = 0; + + while (tIndex < target.Length && pIndex < pattern.Length) + { + p = ConvertCase(pattern[pIndex++], caseSensitive); + + if (pIndex > pattern.Length) + { + return (tIndex >= target.Length); // if end of string true + } + + switch (p) + { + // match zero or more char. + case '*': + { + while (pIndex < pattern.Length && pattern[pIndex] == '*') + { + pIndex++; + } + + while (tIndex < target.Length) + { + if (Match(target.Substring(tIndex++), pattern.Substring(pIndex), caseSensitive)) + { + return true; + } + } + + return Match(target, pattern.Substring(pIndex), caseSensitive); + } + + // match any one char. + case '?': + { + // check if end of string when looking for a single character. + if (tIndex >= target.Length) + { + return false; + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length - 1) + { + return false; + } + + tIndex++; + break; + } + + // match char set + case '[': + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (tIndex > target.Length) + { + return false; // syntax + } + + l = '\0'; + + // match a char if NOT in set [] + if (pattern[pIndex] == '!') + { + ++pIndex; + + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then + { + break; // no match found + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + return false; // if in range, return false + } + } + + l = p; + + if (c == p) // if char matches this element + { + return false; // return false + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + } + + // match if char is in set [] + else + { + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then no match found + { + return false; + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + break; // if in range, move on + } + } + + l = p; + + if (c == p) // if char matches this element move on + { + break; + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + + while (pIndex < pattern.Length && p != ']') // got a match in char set skip to end of set + { + p = pattern[pIndex++]; + } + } + + break; + } + + // match digit. + case '#': + { + c = target[tIndex++]; + + if (!char.IsDigit(c)) + { + return false; // not a digit + } + + break; + } + + // match exact char. + default: + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (c != p) // check for exact char + { + return false; // not a match + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length - 1) + { + return false; + } + + break; + } + } + } + + return true; + } + + // ConvertCase + private static char ConvertCase(char c, bool caseSensitive) + { + return (caseSensitive) ? c : char.ToUpper(c); + } + + /// + /// Unmarshals and frees an array of HRESULTs. + /// + public static int[] GetStatusCodes(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + // unmarshal HRESULT array. + var output = new int[size]; + Marshal.Copy(pArray, output, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return output; + } + + /// + /// Unmarshals and frees an array of 32 bit integers. + /// + public static int[] GetInt32s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new int[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Unmarshals and frees an array of 32 bit integers. + /// + public static int[] GetUInt32s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new int[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Allocates and marshals an array of 32 bit integers. + /// + public static IntPtr GetInt32s(int[] input) + { + var output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Unmarshals and frees a array of 16 bit integers. + /// + public static short[] GetInt16s(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var array = new short[size]; + Marshal.Copy(pArray, array, 0, size); + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return array; + } + + /// + /// Allocates and marshals an array of 16 bit integers. + /// + public static IntPtr GetInt16s(short[] input) + { + var output = IntPtr.Zero; + + if (input != null) + { + output = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(short)) * input.Length); + Marshal.Copy(input, 0, output, input.Length); + } + + return output; + } + + /// + /// Marshals an array of strings into a unmanaged memory buffer + /// + /// The array of strings to marshal + /// The pointer to the unmanaged memory buffer + public static IntPtr GetUnicodeStrings(string[] values) + { + var size = (values != null) ? values.Length : 0; + + if (size <= 0) + { + return IntPtr.Zero; + } + + var pointers = new int[size]; + + for (var ii = 0; ii < size; ii++) + { + pointers[ii] = (int)Marshal.StringToCoTaskMemUni(values[ii]); + } + + var pValues = Marshal.AllocCoTaskMem(values.Length * Marshal.SizeOf(typeof(IntPtr))); + Marshal.Copy(pointers, 0, pValues, size); + + return pValues; + } + + /// + /// Unmarshals and frees a array of unicode strings. + /// + public static string[] GetUnicodeStrings(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var pointers = new IntPtr[size]; + Marshal.Copy(pArray, pointers, 0, size); + + var strings = new string[size]; + + for (var ii = 0; ii < size; ii++) + { + var pString = pointers[ii]; + strings[ii] = Marshal.PtrToStringUni(pString); + if (deallocate) + { + Marshal.FreeCoTaskMem(pString); + } + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return strings; + } + + /// + /// Marshals a DateTime as a WIN32 FILETIME. + /// + /// The DateTime object to marshal + /// The WIN32 FILETIME + public static OpcRcw.Da.FILETIME GetFILETIME(DateTime datetime) + { + OpcRcw.Da.FILETIME filetime; + + if (datetime <= FILETIME_BaseTime) + { + filetime.dwHighDateTime = 0; + filetime.dwLowDateTime = 0; + return filetime; + } + + // adjust for WIN32 FILETIME base. + long ticks; + if (preserveUtc_) + { + ticks = datetime.Subtract(new TimeSpan(FILETIME_BaseTime.Ticks)).Ticks; + } + else + { + ticks = (datetime.ToUniversalTime().Subtract(new TimeSpan(FILETIME_BaseTime.Ticks))).Ticks; + } + + filetime.dwHighDateTime = (int)((ticks >> 32) & 0xFFFFFFFF); + filetime.dwLowDateTime = (int)(ticks & 0xFFFFFFFF); + + return filetime; + } + + /// + /// Unmarshals a WIN32 FILETIME from a pointer. + /// + /// A pointer to a FILETIME structure. + /// A DateTime object. + public static DateTime GetDateTime(IntPtr pFiletime) + { + if (pFiletime == IntPtr.Zero) + { + return DateTime.MinValue; + } + + return GetDateTime((OpcRcw.Da.FILETIME)Marshal.PtrToStructure(pFiletime, typeof(OpcRcw.Da.FILETIME))); + } + + /// + /// Unmarshals a WIN32 FILETIME. + /// + public static DateTime GetDateTime(OpcRcw.Da.FILETIME filetime) + { + // check for invalid value. + if (filetime.dwHighDateTime < 0) + { + return DateTime.MinValue; + } + + // convert FILETIME structure to a 64 bit integer. + long buffer = filetime.dwHighDateTime; + + if (buffer < 0) + { + buffer += ((long)uint.MaxValue + 1); + } + + var ticks = (buffer << 32); + + buffer = filetime.dwLowDateTime; + + if (buffer < 0) + { + buffer += ((long)uint.MaxValue + 1); + } + + ticks += buffer; + + // check for invalid value. + if (ticks == 0) + { + return DateTime.MinValue; + } + + // adjust for WIN32 FILETIME base. + if (preserveUtc_) + { + return FILETIME_BaseTime.Add(new TimeSpan(ticks)); + } + else + { + return FILETIME_BaseTime.Add(new TimeSpan(ticks)).ToLocalTime(); + } + } + + /// + /// Marshals an array of DateTimes into an unmanaged array of FILETIMEs + /// + /// The array of DateTimes to marshal + /// The IntPtr array of FILETIMEs + public static IntPtr GetFILETIMEs(DateTime[] datetimes) + { + var count = (datetimes != null) ? datetimes.Length : 0; + + if (count <= 0) + { + return IntPtr.Zero; + } + + var pFiletimes = Marshal.AllocCoTaskMem(count * Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + + var pos = pFiletimes; + + for (var ii = 0; ii < count; ii++) + { + Marshal.StructureToPtr(GetFILETIME(datetimes[ii]), pos, false); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + return pFiletimes; + } + + /// + /// Unmarshals an array of WIN32 FILETIMEs as DateTimes. + /// + public static DateTime[] GetDateTimes(ref IntPtr pArray, int size, bool deallocate) + { + if (pArray == IntPtr.Zero || size <= 0) + { + return null; + } + + var datetimes = new DateTime[size]; + + var pos = pArray; + + for (var ii = 0; ii < size; ii++) + { + datetimes[ii] = GetDateTime(pos); + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pArray); + pArray = IntPtr.Zero; + } + + return datetimes; + } + + /// + /// Unmarshals an array of WIN32 GUIDs as Guid. + /// + public static Guid[] GetGUIDs(ref IntPtr pInput, int size, bool deallocate) + { + if (pInput == IntPtr.Zero || size <= 0) + { + return null; + } + + var guids = new Guid[size]; + + var pos = pInput; + + for (var ii = 0; ii < size; ii++) + { + var input = (GUID)Marshal.PtrToStructure(pInput, typeof(GUID)); + + guids[ii] = new Guid(input.Data1, input.Data2, input.Data3, input.Data4); + + pos = (IntPtr)(pos.ToInt64() + Marshal.SizeOf(typeof(GUID))); + } + + if (deallocate) + { + Marshal.FreeCoTaskMem(pInput); + pInput = IntPtr.Zero; + } + + return guids; + } + + /// + /// Converts an object into a value that can be marshalled to a VARIANT. + /// + /// The object to convert. + /// The converted object. + public static object GetVARIANT(object source) + { + // check for invalid args. + if (source == null || source.GetType() == null) + { + return null; + } + + // convert a decimal array to an object array since decimal arrays can't be converted to a variant. + if (source.GetType() == typeof(decimal[])) + { + var srcArray = (decimal[])source; + var dstArray = new object[srcArray.Length]; + + for (var ii = 0; ii < srcArray.Length; ii++) + { + try + { + dstArray[ii] = (object)srcArray[ii]; + } + catch (Exception) + { + dstArray[ii] = double.NaN; + } + } + + return dstArray; + } + + // no conversion required. + return source; + } + + /// + /// Converts a LCID to a Locale string. + /// + public static string GetLocale(int input) + { + try + { + if (input == LOCALE_SYSTEM_DEFAULT || input == LOCALE_USER_DEFAULT || input == 0) + { + return CultureInfo.InvariantCulture.Name; + } + + return new CultureInfo(input).Name; + } + catch + { + throw new ExternalException("Invalid LCID", OpcResult.E_INVALIDARG.Code); + } + } + + /// + /// Converts a Locale string to a LCID. + /// + public static int GetLocale(string input) + { + // check for the default culture. + if (input == null || input == "") + { + return 0; + } + + CultureInfo locale; + try { locale = new CultureInfo(input); } + catch { locale = CultureInfo.CurrentCulture; } + + return locale.LCID; + } + + /// + /// Converts the VARTYPE to a system type. + /// + public static Type GetType(VarEnum input) + { + switch (input) + { + case VarEnum.VT_EMPTY: return null; + case VarEnum.VT_I1: return typeof(sbyte); + case VarEnum.VT_UI1: return typeof(byte); + case VarEnum.VT_I2: return typeof(short); + case VarEnum.VT_UI2: return typeof(ushort); + case VarEnum.VT_I4: return typeof(int); + case VarEnum.VT_UI4: return typeof(uint); + case VarEnum.VT_I8: return typeof(long); + case VarEnum.VT_UI8: return typeof(ulong); + case VarEnum.VT_R4: return typeof(float); + case VarEnum.VT_R8: return typeof(double); + case VarEnum.VT_CY: return typeof(decimal); + case VarEnum.VT_BOOL: return typeof(bool); + case VarEnum.VT_DATE: return typeof(DateTime); + case VarEnum.VT_BSTR: return typeof(string); + case VarEnum.VT_ARRAY | VarEnum.VT_I1: return typeof(sbyte[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI1: return typeof(byte[]); + case VarEnum.VT_ARRAY | VarEnum.VT_I2: return typeof(short[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI2: return typeof(ushort[]); + case VarEnum.VT_ARRAY | VarEnum.VT_I4: return typeof(int[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI4: return typeof(uint[]); + case VarEnum.VT_ARRAY | VarEnum.VT_I8: return typeof(long[]); + case VarEnum.VT_ARRAY | VarEnum.VT_UI8: return typeof(ulong[]); + case VarEnum.VT_ARRAY | VarEnum.VT_R4: return typeof(float[]); + case VarEnum.VT_ARRAY | VarEnum.VT_R8: return typeof(double[]); + case VarEnum.VT_ARRAY | VarEnum.VT_CY: return typeof(decimal[]); + case VarEnum.VT_ARRAY | VarEnum.VT_BOOL: return typeof(bool[]); + case VarEnum.VT_ARRAY | VarEnum.VT_DATE: return typeof(DateTime[]); + case VarEnum.VT_ARRAY | VarEnum.VT_BSTR: return typeof(string[]); + case VarEnum.VT_ARRAY | VarEnum.VT_VARIANT: return typeof(object[]); + default: return OpcType.ILLEGAL_TYPE; + } + } + + /// + /// Converts the system type to a VARTYPE. + /// + public static VarEnum GetType(Type input) + { + if (input == null) return VarEnum.VT_EMPTY; + if (input == typeof(sbyte)) return VarEnum.VT_I1; + if (input == typeof(byte)) return VarEnum.VT_UI1; + if (input == typeof(short)) return VarEnum.VT_I2; + if (input == typeof(ushort)) return VarEnum.VT_UI2; + if (input == typeof(int)) return VarEnum.VT_I4; + if (input == typeof(uint)) return VarEnum.VT_UI4; + if (input == typeof(long)) return VarEnum.VT_I8; + if (input == typeof(ulong)) return VarEnum.VT_UI8; + if (input == typeof(float)) return VarEnum.VT_R4; + if (input == typeof(double)) return VarEnum.VT_R8; + if (input == typeof(decimal)) return VarEnum.VT_CY; + if (input == typeof(bool)) return VarEnum.VT_BOOL; + if (input == typeof(DateTime)) return VarEnum.VT_DATE; + if (input == typeof(string)) return VarEnum.VT_BSTR; + if (input == typeof(object)) return VarEnum.VT_EMPTY; + if (input == typeof(sbyte[])) return VarEnum.VT_ARRAY | VarEnum.VT_I1; + if (input == typeof(byte[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI1; + if (input == typeof(short[])) return VarEnum.VT_ARRAY | VarEnum.VT_I2; + if (input == typeof(ushort[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI2; + if (input == typeof(int[])) return VarEnum.VT_ARRAY | VarEnum.VT_I4; + if (input == typeof(uint[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI4; + if (input == typeof(long[])) return VarEnum.VT_ARRAY | VarEnum.VT_I8; + if (input == typeof(ulong[])) return VarEnum.VT_ARRAY | VarEnum.VT_UI8; + if (input == typeof(float[])) return VarEnum.VT_ARRAY | VarEnum.VT_R4; + if (input == typeof(double[])) return VarEnum.VT_ARRAY | VarEnum.VT_R8; + if (input == typeof(decimal[])) return VarEnum.VT_ARRAY | VarEnum.VT_CY; + if (input == typeof(bool[])) return VarEnum.VT_ARRAY | VarEnum.VT_BOOL; + if (input == typeof(DateTime[])) return VarEnum.VT_ARRAY | VarEnum.VT_DATE; + if (input == typeof(string[])) return VarEnum.VT_ARRAY | VarEnum.VT_BSTR; + if (input == typeof(object[])) return VarEnum.VT_ARRAY | VarEnum.VT_VARIANT; + + // check for special types. + if (input == OpcType.ILLEGAL_TYPE) return (VarEnum)Enum.ToObject(typeof(VarEnum), 0x7FFF); + if (input == typeof(Type)) return VarEnum.VT_I2; + if (input == typeof(TsCDaQuality)) return VarEnum.VT_I2; + if (input == typeof(TsDaAccessRights)) return VarEnum.VT_I4; + if (input == typeof(TsDaEuType)) return VarEnum.VT_I4; + return VarEnum.VT_EMPTY; + } + + /// + /// Converts the HRESULT to a system type. + /// + public static OpcResult GetResultId(int input) + { + switch (input) + { + // data access. + case Com.Da.Result.S_OK: return new OpcResult(OpcResult.S_OK, input); + case Com.Da.Result.E_FAIL: return new OpcResult(OpcResult.E_FAIL, input); + case Com.Da.Result.E_INVALIDARG: return new OpcResult(OpcResult.E_INVALIDARG, input); + case Com.Da.Result.DISP_E_TYPEMISMATCH: return new OpcResult(OpcResult.Da.E_BADTYPE, input); + case Com.Da.Result.DISP_E_OVERFLOW: return new OpcResult(OpcResult.Da.E_RANGE, input); + case Com.Da.Result.E_OUTOFMEMORY: return new OpcResult(OpcResult.E_OUTOFMEMORY, input); + case Com.Da.Result.E_NOINTERFACE: return new OpcResult(OpcResult.E_NOTSUPPORTED, input); + case Com.Da.Result.E_INVALIDHANDLE: return new OpcResult(OpcResult.Da.E_INVALIDHANDLE, input); + case Com.Da.Result.E_BADTYPE: return new OpcResult(OpcResult.Da.E_BADTYPE, input); + case Com.Da.Result.E_UNKNOWNITEMID: return new OpcResult(OpcResult.Da.E_UNKNOWN_ITEM_NAME, input); + case Com.Da.Result.E_INVALIDITEMID: return new OpcResult(OpcResult.Da.E_INVALID_ITEM_NAME, input); + case Com.Da.Result.E_UNKNOWNPATH: return new OpcResult(OpcResult.Da.E_UNKNOWN_ITEM_PATH, input); + case Com.Da.Result.E_INVALIDFILTER: return new OpcResult(OpcResult.Da.E_INVALID_FILTER, input); + case Com.Da.Result.E_RANGE: return new OpcResult(OpcResult.Da.E_RANGE, input); + case Com.Da.Result.E_DUPLICATENAME: return new OpcResult(OpcResult.Da.E_DUPLICATENAME, input); + case Com.Da.Result.S_UNSUPPORTEDRATE: return new OpcResult(OpcResult.Da.S_UNSUPPORTEDRATE, input); + case Com.Da.Result.S_CLAMP: return new OpcResult(OpcResult.Da.S_CLAMP, input); + case Com.Da.Result.E_INVALID_PID: return new OpcResult(OpcResult.Da.E_INVALID_PID, input); + case Com.Da.Result.E_DEADBANDNOTSUPPORTED: return new OpcResult(OpcResult.Da.E_NO_ITEM_DEADBAND, input); + case Com.Da.Result.E_NOBUFFERING: return new OpcResult(OpcResult.Da.E_NO_ITEM_BUFFERING, input); + case Com.Da.Result.E_NOTSUPPORTED: return new OpcResult(OpcResult.Da.E_NO_WRITEQT, input); + case Com.Da.Result.E_INVALIDCONTINUATIONPOINT: return new OpcResult(OpcResult.Da.E_INVALIDCONTINUATIONPOINT, input); + case Com.Da.Result.S_DATAQUEUEOVERFLOW: return new OpcResult(OpcResult.Da.S_DATAQUEUEOVERFLOW, input); + + // complex data. + case Com.Cpx.Result.E_TYPE_CHANGED: return new OpcResult(OpcResult.Cpx.E_TYPE_CHANGED, input); + case Com.Cpx.Result.E_FILTER_DUPLICATE: return new OpcResult(OpcResult.Cpx.E_FILTER_DUPLICATE, input); + case Com.Cpx.Result.E_FILTER_INVALID: return new OpcResult(OpcResult.Cpx.E_FILTER_INVALID, input); + case Com.Cpx.Result.E_FILTER_ERROR: return new OpcResult(OpcResult.Cpx.E_FILTER_ERROR, input); + case Com.Cpx.Result.S_FILTER_NO_DATA: return new OpcResult(OpcResult.Cpx.S_FILTER_NO_DATA, input); + + // historical data access. + case Com.Hda.Result.E_MAXEXCEEDED: return new OpcResult(OpcResult.Hda.E_MAXEXCEEDED, input); + case Com.Hda.Result.S_NODATA: return new OpcResult(OpcResult.Hda.S_NODATA, input); + case Com.Hda.Result.S_MOREDATA: return new OpcResult(OpcResult.Hda.S_MOREDATA, input); + case Com.Hda.Result.E_INVALIDAGGREGATE: return new OpcResult(OpcResult.Hda.E_INVALIDAGGREGATE, input); + case Com.Hda.Result.S_CURRENTVALUE: return new OpcResult(OpcResult.Hda.S_CURRENTVALUE, input); + case Com.Hda.Result.S_EXTRADATA: return new OpcResult(OpcResult.Hda.S_EXTRADATA, input); + case Com.Hda.Result.W_NOFILTER: return new OpcResult(OpcResult.Hda.W_NOFILTER, input); + case Com.Hda.Result.E_UNKNOWNATTRID: return new OpcResult(OpcResult.Hda.E_UNKNOWNATTRID, input); + case Com.Hda.Result.E_NOT_AVAIL: return new OpcResult(OpcResult.Hda.E_NOT_AVAIL, input); + case Com.Hda.Result.E_INVALIDDATATYPE: return new OpcResult(OpcResult.Hda.E_INVALIDDATATYPE, input); + case Com.Hda.Result.E_DATAEXISTS: return new OpcResult(OpcResult.Hda.E_DATAEXISTS, input); + case Com.Hda.Result.E_INVALIDATTRID: return new OpcResult(OpcResult.Hda.E_INVALIDATTRID, input); + case Com.Hda.Result.E_NODATAEXISTS: return new OpcResult(OpcResult.Hda.E_NODATAEXISTS, input); + case Com.Hda.Result.S_INSERTED: return new OpcResult(OpcResult.Hda.S_INSERTED, input); + case Com.Hda.Result.S_REPLACED: return new OpcResult(OpcResult.Hda.S_REPLACED, input); + + // Alarms and Events. + case Com.Ae.Result.S_ALREADYACKED: return new OpcResult(OpcResult.Ae.S_ALREADYACKED, input); + case Com.Ae.Result.S_INVALIDBUFFERTIME: return new OpcResult(OpcResult.Ae.S_INVALIDBUFFERTIME, input); + case Com.Ae.Result.S_INVALIDMAXSIZE: return new OpcResult(OpcResult.Ae.S_INVALIDMAXSIZE, input); + case Com.Ae.Result.S_INVALIDKEEPALIVETIME: return new OpcResult(OpcResult.Ae.S_INVALIDKEEPALIVETIME, input); + + // This function returns Da.Result.E_INVALID_PID. AE specific code must map to E_INVALIDBRANCHNAME. + // case Technosoftware.DaAeHdaClient.Com.Ae.Result.E_INVALIDBRANCHNAME: return new OpcResult(OpcResult.Ae.E_INVALIDBRANCHNAME, input); + + case Com.Ae.Result.E_INVALIDTIME: return new OpcResult(OpcResult.Ae.E_INVALIDTIME, input); + case Com.Ae.Result.E_BUSY: return new OpcResult(OpcResult.Ae.E_BUSY, input); + case Com.Ae.Result.E_NOINFO: return new OpcResult(OpcResult.Ae.E_NOINFO, input); + + default: + { + // check for RPC error. + if ((input & 0x7FFF0000) == 0x00010000) + { + return new OpcResult(OpcResult.E_NETWORK_ERROR, input); + } + + // chekc for success code. + if (input >= 0) + { + return new OpcResult(OpcResult.S_FALSE, input); + } + + // return generic error. + return new OpcResult(OpcResult.E_FAIL, input); + } + } + } + + /// + /// Converts a result id to an HRESULT. + /// + public static int GetResultID(OpcResult input) + { + // data access. + if (input.Name == null) + { + return OpcResult.E_FAIL.Code; + } + else if (input.Name != null && input.Name.Namespace == OpcNamespace.OPC_DATA_ACCESS) + { + if (input == OpcResult.S_OK) return Com.Da.Result.S_OK; + if (input == OpcResult.E_FAIL) return Com.Da.Result.E_FAIL; + if (input == OpcResult.E_INVALIDARG) return Com.Da.Result.E_INVALIDARG; + if (input == OpcResult.Da.E_BADTYPE) return Com.Da.Result.E_BADTYPE; + if (input == OpcResult.Da.E_READONLY) return Com.Da.Result.E_BADRIGHTS; + if (input == OpcResult.Da.E_WRITEONLY) return Com.Da.Result.E_BADRIGHTS; + if (input == OpcResult.Da.E_RANGE) return Com.Da.Result.E_RANGE; + if (input == OpcResult.E_OUTOFMEMORY) return Com.Da.Result.E_OUTOFMEMORY; + if (input == OpcResult.E_NOTSUPPORTED) return Com.Da.Result.E_NOINTERFACE; + if (input == OpcResult.Da.E_INVALIDHANDLE) return Com.Da.Result.E_INVALIDHANDLE; + if (input == OpcResult.Da.E_UNKNOWN_ITEM_NAME) return Com.Da.Result.E_UNKNOWNITEMID; + if (input == OpcResult.Da.E_INVALID_ITEM_NAME) return Com.Da.Result.E_INVALIDITEMID; + if (input == OpcResult.Da.E_INVALID_ITEM_PATH) return Com.Da.Result.E_INVALIDITEMID; + if (input == OpcResult.Da.E_UNKNOWN_ITEM_PATH) return Com.Da.Result.E_UNKNOWNPATH; + if (input == OpcResult.Da.E_INVALID_FILTER) return Com.Da.Result.E_INVALIDFILTER; + if (input == OpcResult.Da.S_UNSUPPORTEDRATE) return Com.Da.Result.S_UNSUPPORTEDRATE; + if (input == OpcResult.Da.S_CLAMP) return Com.Da.Result.S_CLAMP; + if (input == OpcResult.Da.E_INVALID_PID) return Com.Da.Result.E_INVALID_PID; + if (input == OpcResult.Da.E_NO_ITEM_DEADBAND) return Com.Da.Result.E_DEADBANDNOTSUPPORTED; + if (input == OpcResult.Da.E_NO_ITEM_BUFFERING) return Com.Da.Result.E_NOBUFFERING; + if (input == OpcResult.Da.E_NO_WRITEQT) return Com.Da.Result.E_NOTSUPPORTED; + if (input == OpcResult.Da.E_INVALIDCONTINUATIONPOINT) return Com.Da.Result.E_INVALIDCONTINUATIONPOINT; + if (input == OpcResult.Da.S_DATAQUEUEOVERFLOW) return Com.Da.Result.S_DATAQUEUEOVERFLOW; + } + + // complex data. + else if (input.Name != null && input.Name.Namespace == OpcNamespace.OPC_COMPLEX_DATA) + { + if (input == OpcResult.Cpx.E_TYPE_CHANGED) return Com.Cpx.Result.E_TYPE_CHANGED; + if (input == OpcResult.Cpx.E_FILTER_DUPLICATE) return Com.Cpx.Result.E_FILTER_DUPLICATE; + if (input == OpcResult.Cpx.E_FILTER_INVALID) return Com.Cpx.Result.E_FILTER_INVALID; + if (input == OpcResult.Cpx.E_FILTER_ERROR) return Com.Cpx.Result.E_FILTER_ERROR; + if (input == OpcResult.Cpx.S_FILTER_NO_DATA) return Com.Cpx.Result.S_FILTER_NO_DATA; + } + + // historical data access. + else if (input.Name != null && input.Name.Namespace == OpcNamespace.OPC_HISTORICAL_DATA_ACCESS) + { + if (input == OpcResult.Hda.E_MAXEXCEEDED) return Com.Hda.Result.E_MAXEXCEEDED; + if (input == OpcResult.Hda.S_NODATA) return Com.Hda.Result.S_NODATA; + if (input == OpcResult.Hda.S_MOREDATA) return Com.Hda.Result.S_MOREDATA; + if (input == OpcResult.Hda.E_INVALIDAGGREGATE) return Com.Hda.Result.E_INVALIDAGGREGATE; + if (input == OpcResult.Hda.S_CURRENTVALUE) return Com.Hda.Result.S_CURRENTVALUE; + if (input == OpcResult.Hda.S_EXTRADATA) return Com.Hda.Result.S_EXTRADATA; + if (input == OpcResult.Hda.E_UNKNOWNATTRID) return Com.Hda.Result.E_UNKNOWNATTRID; + if (input == OpcResult.Hda.E_NOT_AVAIL) return Com.Hda.Result.E_NOT_AVAIL; + if (input == OpcResult.Hda.E_INVALIDDATATYPE) return Com.Hda.Result.E_INVALIDDATATYPE; + if (input == OpcResult.Hda.E_DATAEXISTS) return Com.Hda.Result.E_DATAEXISTS; + if (input == OpcResult.Hda.E_INVALIDATTRID) return Com.Hda.Result.E_INVALIDATTRID; + if (input == OpcResult.Hda.E_NODATAEXISTS) return Com.Hda.Result.E_NODATAEXISTS; + if (input == OpcResult.Hda.S_INSERTED) return Com.Hda.Result.S_INSERTED; + if (input == OpcResult.Hda.S_REPLACED) return Com.Hda.Result.S_REPLACED; + } + + // check for custom code. + else if (input.Code == -1) + { + // default success code. + if (input.Succeeded()) + { + return OpcResult.S_FALSE.Code; + } + + // default error code. + return OpcResult.E_FAIL.Code; + } + + // return custom code. + return input.Code; + } + + /// + /// Returns an exception after extracting HRESULT from the exception. + /// + public static Exception CreateException(string message, Exception e) + { + return CreateException(message, Marshal.GetHRForException(e)); + } + + /// + /// Returns an exception after extracting HRESULT from the exception. + /// + public static Exception CreateException(string message, int code) + { + return new OpcResultException(GetResultId(code), message); + } + + /// + /// Releases the server if it is a true COM server. + /// + public static void ReleaseServer(object server) + { + if (server != null && server.GetType().IsCOMObject) + { + Marshal.ReleaseComObject(server); + } + } + + /// + /// Retrieves the system message text for the specified error. + /// + public static string GetSystemMessage(int error, int localeId) + { + int langId; + switch (localeId) + { + case LOCALE_SYSTEM_DEFAULT: + { + langId = GetSystemDefaultLangID(); + break; + } + + case LOCALE_USER_DEFAULT: + { + langId = GetUserDefaultLangID(); + break; + } + + default: + { + langId = (0xFFFF & localeId); + break; + } + } + + var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH); + + var result = FormatMessageW( + (int)FORMAT_MESSAGE_FROM_SYSTEM, + IntPtr.Zero, + error, + langId, + buffer, + MAX_MESSAGE_LENGTH - 1, + IntPtr.Zero); + + if (result > 0) + { + var msg = Marshal.PtrToStringUni(buffer); + Marshal.FreeCoTaskMem(buffer); + + if (!string.IsNullOrEmpty(msg)) + { + return msg.Trim(); + } + } + + return $"0x{error:X8}"; + } + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/Attribute.cs b/Technosoftware/DaAeHdaClient/Ae/Attribute.cs new file mode 100644 index 0000000..3589a9a --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/Attribute.cs @@ -0,0 +1,72 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The description of an attribute supported by the server. + /// + [Serializable] + public class TsCAeAttribute : ICloneable + { + #region Properties + /// + /// A unique identifier for the attribute. + /// + public int ID { get; set; } + + /// + /// The unique name for the attribute. + /// + public string Name { get; set; } + + /// + /// The data type of the attribute. + /// + public Type DataType { get; set; } + + #endregion + + #region Public Methods + + /// + /// Returns a string that represents the current object. + /// + /// + public override string ToString() + { + return Name; + } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() { return MemberwiseClone(); } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/AttributeCollection.cs b/Technosoftware/DaAeHdaClient/Ae/AttributeCollection.cs new file mode 100644 index 0000000..7db6677 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/AttributeCollection.cs @@ -0,0 +1,62 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Contains a writable collection attribute ids. + /// + [Serializable] + public class TsCAeAttributeCollection : OpcWriteableCollection + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal TsCAeAttributeCollection() : base(null, typeof(int)) { } + + /// + /// Creates a collection from an array. + /// + internal TsCAeAttributeCollection(int[] attributeIDs) : base(attributeIDs, typeof(int)) { } + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new int this[int index] => (int)Array[index]; + + /// + /// Returns a copy of the collection as an array. + /// + public new int[] ToArray() + { + return (int[])Array.ToArray(typeof(int)); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/AttributeDictionary.cs b/Technosoftware/DaAeHdaClient/Ae/AttributeDictionary.cs new file mode 100644 index 0000000..6ca46b1 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/AttributeDictionary.cs @@ -0,0 +1,91 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Contains multiple lists of the attributes indexed by category. + /// + [Serializable] + public sealed class TsCAeAttributeDictionary : OpcWriteableDictionary + { + #region Constructors, Destructor, Initialization + /// + /// Constructs an empty dictionary. + /// + public TsCAeAttributeDictionary() : base(null, typeof(int), typeof(TsCAeAttributeCollection)) { } + + /// + /// Constructs an dictionary from a set of category ids. + /// + public TsCAeAttributeDictionary(int[] categoryIds) + : base(null, typeof(int), typeof(TsCAeAttributeCollection)) + { + foreach (var categoryId in categoryIds) + { + Add(categoryId, null); + } + } + #endregion + + #region Public Methods + /// + /// Gets or sets the attribute collection for the specified category. + /// + public TsCAeAttributeCollection this[int categoryId] + { + get => (TsCAeAttributeCollection)base[categoryId]; + + set + { + if (value != null) + { + base[categoryId] = value; + } + else + { + base[categoryId] = new TsCAeAttributeCollection(); + } + } + } + + /// + /// Adds an element with the provided key and value to the IDictionary. + /// + public void Add(int key, int[] value) + { + if (value != null) + { + base.Add(key, new TsCAeAttributeCollection(value)); + } + else + { + base.Add(key, new TsCAeAttributeCollection()); + } + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/AttributeValue.cs b/Technosoftware/DaAeHdaClient/Ae/AttributeValue.cs new file mode 100644 index 0000000..b5fe9f1 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/AttributeValue.cs @@ -0,0 +1,79 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The value of an attribute for an event source. + /// + [Serializable] + public class TsCAeAttributeValue : ICloneable, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Properties + /// + /// A unique identifier for the attribute. + /// + public int ID { get; set; } + + /// + /// The attribute value. + /// + public object Value { get; set; } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an property. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCAeAttributeValue)MemberwiseClone(); + clone.Value = OpcConvert.Clone(Value); + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/BrowseElement.cs b/Technosoftware/DaAeHdaClient/Ae/BrowseElement.cs new file mode 100644 index 0000000..350bf56 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/BrowseElement.cs @@ -0,0 +1,70 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Contains a description of an element in the server address space. + /// + [Serializable] + public class TsCAeBrowseElement + { + #region Fields + private TsCAeBrowseType browseType_ = TsCAeBrowseType.Area; + #endregion + + #region Properties + /// + /// A descriptive name for element that is unique within a branch. + /// + public string Name { get; set; } + + /// + /// The fully qualified name for the element. + /// + public string QualifiedName { get; set; } + + /// + /// Whether the element is a source or an area. + /// + public TsCAeBrowseType NodeType + { + get => browseType_; + set => browseType_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + }; +} diff --git a/Technosoftware/DaAeHdaClient/Ae/BrowsePosition.cs b/Technosoftware/DaAeHdaClient/Ae/BrowsePosition.cs new file mode 100644 index 0000000..3c0996f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/BrowsePosition.cs @@ -0,0 +1,129 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Stores the state of a browse operation. + /// + [Serializable] + public class TsCAeBrowsePosition : IOpcBrowsePosition + { + #region Fields + private bool disposed_; + private string areaId_; + private TsCAeBrowseType browseType_; + private string browseFilter_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Saves the parameters for an incomplete browse information. + /// + public TsCAeBrowsePosition( + string areaId, + TsCAeBrowseType browseType, + string browseFilter) + { + areaId_ = areaId; + browseType_ = browseType; + browseFilter_ = browseFilter; + } + + /// + /// The finalizer implementation. + /// + ~TsCAeBrowsePosition() + { + Dispose(false); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + Dispose(true); + // Take yourself off the Finalization queue + // to prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!disposed_) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + } + // Release unmanaged resources. If disposing is false, + // only the following code is executed. + } + disposed_ = true; + } + #endregion + + #region Properties + /// + /// The fully qualified id for the area being browsed. + /// + public string AreaID => areaId_; + + /// + /// The type of child element being returned with the browse. + /// + public TsCAeBrowseType BrowseType => browseType_; + + /// + /// The filter applied to the name of the elements being returned. + /// + public string BrowseFilter => browseFilter_; + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() + { + return (TsCAeBrowsePosition)MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/BrowseType.cs b/Technosoftware/DaAeHdaClient/Ae/BrowseType.cs new file mode 100644 index 0000000..3048ce4 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/BrowseType.cs @@ -0,0 +1,44 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The type of nodes to return during a browse. + /// + public enum TsCAeBrowseType + { + /// + /// Return only nodes that are process areas. + /// + Area, + + /// + /// Return only nodes that are event sources. + /// + Source + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/Category.cs b/Technosoftware/DaAeHdaClient/Ae/Category.cs new file mode 100644 index 0000000..a138b80 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/Category.cs @@ -0,0 +1,63 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The description of an event category supported by the server. + /// + [Serializable] + public class TsCAeCategory : ICloneable + { + #region Properties + /// + /// A unique identifier for the category. + /// + public int ID { get; set; } + + /// + /// The unique name for the category. + /// + public string Name { get; set; } + + /// + /// Returns a string that represents the current object. + /// + /// + public override string ToString() + { + return Name; + } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() { return MemberwiseClone(); } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/ChangeMask.cs b/Technosoftware/DaAeHdaClient/Ae/ChangeMask.cs new file mode 100644 index 0000000..1e5a7c8 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/ChangeMask.cs @@ -0,0 +1,75 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The bits indicating what changes generated an event notification. + /// + [Flags] + public enum TsCAeChangeMask + { + /// + /// The condition’s active state has changed. + /// + ActiveState = 0x0001, + + /// + /// The condition’s acknowledgment state has changed. + /// + AcknowledgeState = 0x0002, + + /// + /// The condition’s enabled state has changed. + /// + EnableState = 0x0004, + + /// + /// The condition quality has changed. + /// + Quality = 0x0008, + + /// + /// The severity level has changed. + /// + Severity = 0x0010, + + /// + /// The condition has transitioned into a new sub-condition. + /// + SubCondition = 0x0020, + + /// + /// The event message has changed. + /// + Message = 0x0040, + + /// + /// One or more event attributes have changed. + /// + Attribute = 0x0080 + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/Condition.cs b/Technosoftware/DaAeHdaClient/Ae/Condition.cs new file mode 100644 index 0000000..440340e --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/Condition.cs @@ -0,0 +1,217 @@ +#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 Technosoftware.DaAeHdaClient.Da; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The description of an item condition state supported by the server. + /// + [Serializable] + public class TsCAeCondition : ICloneable + { + #region Fields + private TsCAeSubCondition _activeSubcondition = new TsCAeSubCondition(); + private TsCDaQuality _quality = TsCDaQuality.Bad; + private DateTime _lastAckTime = DateTime.MinValue; + private DateTime _subCondLastActive = DateTime.MinValue; + private DateTime _condLastActive = DateTime.MinValue; + private DateTime _condLastInactive = DateTime.MinValue; + private SubConditionCollection _subconditions = new SubConditionCollection(); + private AttributeValueCollection _attributes = new AttributeValueCollection(); + #endregion + + #region AttributeCollection Class + /// + /// Contains a read-only collection of AttributeValues. + /// + public class AttributeValueCollection : OpcWriteableCollection + { + /// + /// An indexer for the collection. + /// + public new TsCAeAttributeValue this[int index] => (TsCAeAttributeValue)Array[index]; + + /// + /// Returns a copy of the collection as an array. + /// + public new TsCAeAttributeValue[] ToArray() + { + return (TsCAeAttributeValue[])Array.ToArray(); + } + + /// + /// Creates an empty collection. + /// + internal AttributeValueCollection() : base(null, typeof(TsCAeAttributeValue)) { } + } + #endregion + + #region SubConditionCollection Class + /// + /// Contains a read-only collection of SubConditions. + /// + public class SubConditionCollection : OpcWriteableCollection + { + /// + /// An indexer for the collection. + /// + public new TsCAeSubCondition this[int index] => (TsCAeSubCondition)Array[index]; + + /// + /// Returns a copy of the collection as an array. + /// + public new TsCAeSubCondition[] ToArray() + { + return (TsCAeSubCondition[])Array.ToArray(); + } + + /// + /// Creates an empty collection. + /// + internal SubConditionCollection() : base(null, typeof(TsCAeSubCondition)) { } + } + #endregion + + #region Properties + /// + /// A bit mask indicating the current state of the condition + /// + public int State { get; set; } + + /// + /// The currently active sub-condition, for multi-state conditions which are active. + /// For a single-state condition, this contains the information about the condition itself. + /// For inactive conditions, this value is null. + /// + public TsCAeSubCondition ActiveSubCondition + { + get => _activeSubcondition; + set => _activeSubcondition = value; + } + + /// + /// The quality associated with the condition state. + /// + public TsCDaQuality Quality + { + get => _quality; + set => _quality = value; + } + + /// + /// The time of the most recent acknowledgment of this condition (of any sub-condition). + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime LastAckTime + { + get => _lastAckTime; + set => _lastAckTime = value; + } + + /// + /// Time of the most recent transition into active sub-condition. + /// This is the time value which must be specified when acknowledging the condition. + /// If the condition has never been active, this value is DateTime.MinValue. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime SubCondLastActive + { + get => _subCondLastActive; + set => _subCondLastActive = value; + } + + /// + /// Time of the most recent transition into the condition. + /// There may be transitions among the sub-conditions which are more recent. + /// If the condition has never been active, this value is DateTime.MinValue. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime CondLastActive + { + get => _condLastActive; + set => _condLastActive = value; + } + + /// + /// Time of the most recent transition out of this condition. + /// This value is DateTime.MinValue if the condition has never been active, + /// or if it is currently active for the first time and has never been exited. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime CondLastInactive + { + get => _condLastInactive; + set => _condLastInactive = value; + } + + /// + /// This is the ID of the client who last acknowledged this condition. + /// This value is null if the condition has never been acknowledged. + /// + public string AcknowledgerID { get; set; } + + /// + /// The comment string passed in by the client who last acknowledged this condition. + /// This value is null if the condition has never been acknowledged. + /// + public string Comment { get; set; } + + /// + /// The sub-conditions defined for this condition. + /// For single-state conditions, the collection will contain one element, the value of which describes the condition. + /// + public SubConditionCollection SubConditions => _subconditions; + + /// + /// The values of the attributes requested for this condition. + /// + public AttributeValueCollection Attributes => _attributes; + + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCAeCondition)MemberwiseClone(); + + clone._activeSubcondition = (TsCAeSubCondition)_activeSubcondition.Clone(); + clone._subconditions = (SubConditionCollection)_subconditions.Clone(); + clone._attributes = (AttributeValueCollection)_attributes.Clone(); + + return clone; + } + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/ConditionState.cs b/Technosoftware/DaAeHdaClient/Ae/ConditionState.cs new file mode 100644 index 0000000..818c487 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/ConditionState.cs @@ -0,0 +1,50 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The possible states for a condition. + /// + [Flags] + public enum TsCAeConditionState + { + /// + /// The server is currently checking the state of the condition. + /// + Enabled = 0x0001, + + /// + /// The associated object is in the state represented by the condition. + /// + Active = 0x0002, + + /// + /// The condition has been acknowledged. + /// + Acknowledged = 0x0004 + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/EnabledStateResult.cs b/Technosoftware/DaAeHdaClient/Ae/EnabledStateResult.cs new file mode 100644 index 0000000..db5cff6 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/EnabledStateResult.cs @@ -0,0 +1,95 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The current state of a process area or an event source. + /// + public class TsCAeEnabledStateResult : IOpcResult + { + #region Fields + private string qualifiedName_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public TsCAeEnabledStateResult() { } + + /// + /// Initializes the object with an qualified name. + /// + public TsCAeEnabledStateResult(string qualifiedName) + { + qualifiedName_ = qualifiedName; + } + + /// + /// Initializes the object with an qualified name and Result. + /// + public TsCAeEnabledStateResult(string qualifiedName, OpcResult result) + { + qualifiedName_ = qualifiedName; + Result = result; + } + #endregion + + #region Properties + /// + /// Whether if the area or source is enabled. + /// + public bool Enabled { get; set; } + + /// + /// Whether the area or source is enabled and all areas within the hierarchy of its containing areas are enabled. + /// + public bool EffectivelyEnabled { get; set; } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result { get; set; } = OpcResult.S_OK; + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/EventAcknowledgement.cs b/Technosoftware/DaAeHdaClient/Ae/EventAcknowledgement.cs new file mode 100644 index 0000000..57d73c2 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/EventAcknowledgement.cs @@ -0,0 +1,93 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Specifies the information required to acknowledge an event. + /// + [Serializable] + public class TsCAeEventAcknowledgement : ICloneable + { + #region Fields + private DateTime activeTime_ = DateTime.MinValue; + #endregion + + #region Properties + /// + /// The name of the source that generated the event. + /// + public string SourceName { get; set; } + + /// + /// The name of the condition that is being acknowledged. + /// + public string ConditionName { get; set; } + + /// + /// The time that the condition or sub-condition became active. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime ActiveTime + { + get => activeTime_; + set => activeTime_ = value; + } + + /// + /// The cookie for the condition passed to client during the event notification. + /// + public int Cookie { get; set; } + + /// + /// Constructs an acknowledgment with its default values. + /// + public TsCAeEventAcknowledgement() { } + + /// + /// Constructs an acknowledgment from an event notification. + /// + public TsCAeEventAcknowledgement(TsCAeEventNotification notification) + { + SourceName = notification.SourceID; + ConditionName = notification.ConditionName; + activeTime_ = notification.ActiveTime; + Cookie = notification.Cookie; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/EventNotification.cs b/Technosoftware/DaAeHdaClient/Ae/EventNotification.cs new file mode 100644 index 0000000..0d245c8 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/EventNotification.cs @@ -0,0 +1,275 @@ +#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 Technosoftware.DaAeHdaClient.Da; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// A notification sent by the server when an event change occurs. + /// + [Serializable] + public class TsCAeEventNotification : ICloneable + { + #region Fields + private DateTime time_ = DateTime.MinValue; + private TsCAeEventType eventType_ = TsCAeEventType.Condition; + private int severity_ = 1; + private AttributeCollection attributes_ = new AttributeCollection(); + private TsCDaQuality daQuality_ = TsCDaQuality.Bad; + private DateTime activeTime_ = DateTime.MinValue; + #endregion + + #region AttributeCollection Class + /// + /// Contains a read-only collection of AttributeValues. + /// + [Serializable] + public class AttributeCollection : OpcReadOnlyCollection + { + /// + /// Creates an empty collection. + /// + internal AttributeCollection() : base(new object[0]) { } + + /// + /// Creates a collection from an array of objects. + /// + internal AttributeCollection(object[] attributes) : base(attributes) { } + } + #endregion + + #region Properties + /// + /// The handle of the subscription that requested the notification + /// + public object ClientHandle { get; set; } + + /// + /// The identifier for the source that generated the event. + /// + public string SourceID { get; set; } + + /// + /// The time of the event occurrence. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime Time + { + get => time_; + set => time_ = value; + } + + /// + /// Event notification message describing the event. + /// + public string Message { get; set; } + + /// + /// The type of event that generated the notification. + /// + public TsCAeEventType EventType + { + get => eventType_; + set => eventType_ = value; + } + + /// + /// The vendor defined category id for the event. + /// + public int EventCategory { get; set; } + + /// + /// The severity of the event (1..1000). + /// + public int Severity + { + get => severity_; + set => severity_ = value; + } + + /// + /// The name of the condition related to this event notification. + /// + public string ConditionName { get; set; } + + /// + /// The name of the current sub-condition, for multi-state conditions. + /// For a single-state condition, this contains the condition name. + /// + public string SubConditionName { get; set; } + + /// + /// The values of the attributes selected for the event subscription. + /// + public AttributeCollection Attributes => attributes_; + + /// + /// Indicates which properties of the condition have changed, to have caused the server to send the event notification. + /// + public int ChangeMask { get; set; } + + /// + /// Indicates which properties of the condition have changed, to have caused the server to send the event notification. + /// + // ReSharper disable once UnusedMember.Global + public string ChangeMaskAsText + { + get + { + string str = null; + + if ((ChangeMask & 0x0001) == 0x0001) str = "Active State, "; + if ((ChangeMask & 0x0002) == 0x0002) str += "Ack State, "; + if ((ChangeMask & 0x0004) == 0x0004) str += "Enable State, "; + if ((ChangeMask & 0x0008) == 0x0005) str += "Quality, "; + if ((ChangeMask & 0x0010) == 0x0010) str += "Severity, "; + if ((ChangeMask & 0x0020) == 0x0020) str += "SubCondition, "; + if ((ChangeMask & 0x0040) == 0x0040) str += "Message, "; + if ((ChangeMask & 0x0080) == 0x0080) str += "Attribute"; + + return str; + } + } + + /// + /// A bit mask specifying the new state of the condition. + /// + public int NewState { get; set; } + + /// + /// A bit mask specifying the new state of the condition. + /// + // ReSharper disable once UnusedMember.Global + public string NewStateAsText + { + get + { + string str; + + if ((NewState & 0x0001) == 0x0001) + { + str = "Active, "; + } + else + { + str = "Inactive, "; + } + if ((NewState & 0x0002) == 0x0002) + { + str += "Acknowledged, "; + } + else + { + str += "UnAcknowledged, "; + } + if ((NewState & 0x0004) == 0x0004) + { + str += "Enabled"; + } + else + { + str += "Disabled"; + } + + return str; + } + } + + /// + /// The quality associated with the condition state. + /// + public TsCDaQuality Quality + { + get => daQuality_; + set => daQuality_ = value; + } + + /// + /// Whether the related condition requires acknowledgment of this event. + /// + public bool AckRequired { get; set; } + + /// + /// The time that the condition became active (for single-state conditions), or the + /// time of the transition into the current sub-condition (for multi-state conditions). + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime ActiveTime + { + get => activeTime_; + set => activeTime_ = value; + } + + /// + /// A server defined cookie associated with the event notification. + /// + public int Cookie { get; set; } + + /// + /// For tracking events, this is the actor id for the event notification. + /// For condition-related events, this is the acknowledgment id passed by the client. + /// + public string ActorID { get; set; } + #endregion + + #region Public Methods + /// + /// Sets the list of attribute values. + /// + public void SetAttributes(object[] attributes) + { + if (attributes == null) + { + attributes_ = new AttributeCollection(); + } + else + { + attributes_ = new AttributeCollection(attributes); + } + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCAeEventNotification)MemberwiseClone(); + + clone.attributes_ = (AttributeCollection)attributes_.Clone(); + + return clone; + } + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/EventType.cs b/Technosoftware/DaAeHdaClient/Ae/EventType.cs new file mode 100644 index 0000000..173b0a4 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/EventType.cs @@ -0,0 +1,54 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The types of events that could be generated by a server. + /// + [Flags] + public enum TsCAeEventType + { + /// + /// Events that are not tracking or condition events. + /// + Simple = 0x0001, + + /// + /// Events that represent occurrences which involve the interaction of the client with a target within the server. + /// + Tracking = 0x0002, + + /// + /// Events that are associated with transitions in and out states defined by the server. + /// + Condition = 0x0004, + + /// + /// All events generated by the server. + /// + All = 0xFFFF + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/FilterType.cs b/Technosoftware/DaAeHdaClient/Ae/FilterType.cs new file mode 100644 index 0000000..0a3a2d5 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/FilterType.cs @@ -0,0 +1,65 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The types of event filters that the server could support. + /// + [Flags] + public enum TsCAeFilterType + { + /// + /// The server supports filtering by event type. + /// + Event = 0x0001, + + /// + /// The server supports filtering by event categories. + /// + Category = 0x0002, + + /// + /// The server supports filtering by severity levels. + /// + Severity = 0x0004, + + /// + /// The server supports filtering by process area. + /// + Area = 0x0008, + + /// + /// The server supports filtering by event sources. + /// + Source = 0x0010, + + /// + /// All filters supported by the server. + /// + All = 0xFFFF + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/IServer.cs b/Technosoftware/DaAeHdaClient/Ae/IServer.cs new file mode 100644 index 0000000..13554a5 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/IServer.cs @@ -0,0 +1,202 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Defines functionality that is common to all OPC Alarms and Events servers. + /// + public interface ITsCAeServer : IOpcServer + { + /// + /// Returns the current server status. + /// + /// The current server status. + OpcServerStatus GetServerStatus(); + + /// + /// Creates a new event subscription. + /// + /// The initial state for the subscription. + /// The new subscription object. + ITsCAeSubscription CreateSubscription(TsCAeSubscriptionState state); + + /// + /// Returns the event filters supported by the server. + /// + /// A bit mask of all event filters supported by the server. + int QueryAvailableFilters(); + + /// + /// Returns the event categories supported by the server for the specified event types. + /// + /// A bit mask for the event types of interest. + /// A collection of event categories. + TsCAeCategory[] QueryEventCategories(int eventType); + + /// + /// Returns the condition names supported by the server for the specified event categories. + /// + /// A bit mask for the event categories of interest. + /// A list of condition names. + string[] QueryConditionNames(int eventCategory); + + /// + /// Returns the sub-condition names supported by the server for the specified event condition. + /// + /// The name of the condition. + /// A list of sub-condition names. + string[] QuerySubConditionNames(string conditionName); + + /// + /// Returns the condition names supported by the server for the specified event source. + /// + /// The name of the event source. + /// A list of condition names. + string[] QueryConditionNames(string sourceName); + + /// + /// Returns the event attributes supported by the server for the specified event categories. + /// + /// The event category of interest. + /// A collection of event attributes. + TsCAeAttribute[] QueryEventAttributes(int eventCategory); + + /// + /// Returns the DA item ids for a set of attribute ids belonging to events which meet the specified filter criteria. + /// + /// The event source of interest. + /// The id of the event category for the events of interest. + /// The name of a condition within the event category. + /// The name of a sub-condition within a multi-state condition. + /// The ids of the attributes to return item ids for. + /// A list of item urls for each specified attribute. + TsCAeItemUrl[] TranslateToItemIDs( + string sourceName, + int eventCategory, + string conditionName, + string subConditionName, + int[] attributeIDs); + + /// + /// Returns the current state information for the condition instance corresponding to the source and condition name. + /// + /// The source name + /// A condition name for the source. + /// The list of attributes to return with the condition state. + /// The current state of the connection. + TsCAeCondition GetConditionState( + string sourceName, + string conditionName, + int[] attributeIDs); + + /// + /// Places the specified process areas into the enabled state. + /// + /// A list of fully qualified area names. + /// The results of the operation for each area. + OpcResult[] EnableConditionByArea(string[] areas); + + /// + /// Places the specified process areas into the disabled state. + /// + /// A list of fully qualified area names. + /// The results of the operation for each area. + OpcResult[] DisableConditionByArea(string[] areas); + + /// + /// Places the specified process areas into the enabled state. + /// + /// A list of fully qualified source names. + /// The results of the operation for each area. + OpcResult[] EnableConditionBySource(string[] sources); + + /// + /// Places the specified process areas into the disabled state. + /// + /// A list of fully qualified source names. + /// The results of the operation for each area. + OpcResult[] DisableConditionBySource(string[] sources); + + /// + /// Returns the enabled state for the specified process areas. + /// + /// A list of fully qualified area names. + TsCAeEnabledStateResult[] GetEnableStateByArea(string[] areas); + + /// + /// Returns the enabled state for the specified event sources. + /// + /// A list of fully qualified source names. + TsCAeEnabledStateResult[] GetEnableStateBySource(string[] sources); + + /// + /// Used to acknowledge one or more conditions in the event server. + /// + /// The identifier for who is acknowledging the condition. + /// A comment associated with the acknowledgment. + /// The conditions being acknowledged. + /// A list of result id indictaing whether each condition was successfully acknowledged. + OpcResult[] AcknowledgeCondition( + string acknowledgerID, + string comment, + TsCAeEventAcknowledgement[] conditions); + + /// + /// Browses for all children of the specified area that meet the filter criteria. + /// + /// The full-qualified id for the area. + /// The type of children to return. + /// The expression used to filter the names of children returned. + /// The set of elements that meet the filter criteria. + TsCAeBrowseElement[] Browse( + string areaID, + TsCAeBrowseType browseType, + string browseFilter); + + /// + /// Browses for all children of the specified area that meet the filter criteria. + /// + /// The full-qualified id for the area. + /// The type of children to return. + /// The expression used to filter the names of children returned. + /// The maximum number of elements to return. + /// The object used to continue the browse if the number nodes exceeds the maximum specified. + /// The set of elements that meet the filter criteria. + TsCAeBrowseElement[] Browse( + string areaID, + TsCAeBrowseType browseType, + string browseFilter, + int maxElements, + out IOpcBrowsePosition position); + + /// + /// Continues browsing the server's address space at the specified position. + /// + /// The maximum number of elements to return. + /// The position object used to continue a browse operation. + /// The set of elements that meet the filter criteria. + TsCAeBrowseElement[] BrowseNext(int maxElements, ref IOpcBrowsePosition position); + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/ISubscription.cs b/Technosoftware/DaAeHdaClient/Ae/ISubscription.cs new file mode 100644 index 0000000..2bea031 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/ISubscription.cs @@ -0,0 +1,109 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// An interface to an object which implements a AE event subscription. + /// + public interface ITsCAeSubscription : IDisposable + { + #region Events + /// + /// An event to receive event change updates. + /// + event TsCAeDataChangedEventHandler DataChangedEvent; + #endregion + + #region State Management + /// + /// Returns the current state of the subscription. + /// + /// The current state of the subscription. + TsCAeSubscriptionState GetState(); + + /// + /// Changes the state of a subscription. + /// + /// A bit mask that indicates which elements of the subscription state are changing. + /// The new subscription state. + /// The actual subscription state after applying the changes. + TsCAeSubscriptionState ModifyState(int masks, TsCAeSubscriptionState state); + #endregion + + #region Filter Management + /// + /// Returns the current filters for the subscription. + /// + /// The current filters for the subscription. + TsCAeSubscriptionFilters GetFilters(); + + /// + /// Sets the current filters for the subscription. + /// + /// The new filters to use for the subscription. + void SetFilters(TsCAeSubscriptionFilters filters); + #endregion + + #region Attribute Management + /// + /// Returns the set of attributes to return with event notifications. + /// + /// The specific event category for which the attributes apply. + /// The set of attribute ids which returned with event notifications. + int[] GetReturnedAttributes(int eventCategory); + + /// + /// Selects the set of attributes to return with event notifications. + /// + /// The specific event category for which the attributes apply. + /// The list of attribute ids to return. + void SelectReturnedAttributes(int eventCategory, int[] attributeIDs); + #endregion + + #region Refresh + /// + /// Force a refresh for all active conditions and inactive, unacknowledged conditions whose event notifications match the filter of the event subscription. + /// + void Refresh(); + + /// + /// Cancels an outstanding refresh request. + /// + void CancelRefresh(); + #endregion + } + + #region Delegate Declarations + /// + /// A delegate to receive data change updates from the server. + /// + /// The notifications sent by the server when a event change occurs. + /// TRUE if this is a subscription refresh + /// TRUE if this is the last subscription refresh in response to a specific invocation of the Refresh method. + public delegate void TsCAeDataChangedEventHandler(TsCAeEventNotification[] notifications, bool refresh, bool lastRefresh); + #endregion +} diff --git a/Technosoftware/DaAeHdaClient/Ae/ItemUrl.cs b/Technosoftware/DaAeHdaClient/Ae/ItemUrl.cs new file mode 100644 index 0000000..9ed0f7c --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/ItemUrl.cs @@ -0,0 +1,93 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The item id and network location of a DA item associated with an event source. + /// + [Serializable] + public class TsCAeItemUrl : OpcItem + { + #region Fields + private OpcUrl url_ = new OpcUrl(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public TsCAeItemUrl() {} + + /// + /// Initializes the object with an ItemIdentifier object. + /// + public TsCAeItemUrl(OpcItem item) : base(item) {} + + /// + /// Initializes the object with an ItemIdentifier object and url. + /// + public TsCAeItemUrl(OpcItem item, OpcUrl url) + : base(item) + { + Url = url; + } + + /// + /// Initializes object with the specified ItemResult object. + /// + public TsCAeItemUrl(TsCAeItemUrl item) : base(item) + { + if (item != null) + { + Url = item.Url; + } + } + #endregion + + #region Properties + /// + /// The url of the server that contains the item. + /// + public OpcUrl Url + { + get => url_; + set => url_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + return new TsCAeItemUrl(this); + } + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/ItemUrlCollection.cs b/Technosoftware/DaAeHdaClient/Ae/ItemUrlCollection.cs new file mode 100644 index 0000000..86b76af --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/ItemUrlCollection.cs @@ -0,0 +1,60 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Contains a collection of item urls. + /// + internal class TsCAeItemUrlCollection : OpcReadOnlyCollection + { + #region Constructors, Destructor, Initialization + /// + /// Constructs an empty collection. + /// + public TsCAeItemUrlCollection() : base(new TsCAeItemUrl[0]) { } + + /// + /// Constructs a collection from an array of item urls. + /// + public TsCAeItemUrlCollection(TsCAeItemUrl[] itemUrls) : base(itemUrls) { } + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new TsCAeItemUrl this[int index] => (TsCAeItemUrl)Array.GetValue(index); + + /// + /// Returns a copy of the collection as an array. + /// + public new TsCAeItemUrl[] ToArray() + { + return (TsCAeItemUrl[])OpcConvert.Clone(Array); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/Server.cs b/Technosoftware/DaAeHdaClient/Ae/Server.cs new file mode 100644 index 0000000..5432323 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/Server.cs @@ -0,0 +1,661 @@ +#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; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// An in-process object which provides access to AE server objects. + /// + [Serializable] + public class TsCAeServer : OpcServer, ITsCAeServer + { + #region SubscriptionCollection Class + /// + /// A read-only collection of subscriptions. + /// + public class SubscriptionCollection : OpcReadOnlyCollection + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal SubscriptionCollection() : base(new TsCAeSubscription[0]) { } + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new TsCAeSubscription this[int index] => (TsCAeSubscription)Array.GetValue(index); + + /// + /// Returns a copy of the collection as an array. + /// + public new TsCAeSubscription[] ToArray() + { + return (TsCAeSubscription[])Array; + } + + /// + /// Adds a subscription to the end of the collection. + /// + internal void Add(TsCAeSubscription subscription) + { + var array = new TsCAeSubscription[Count + 1]; + + Array.CopyTo(array, 0); + array[Count] = subscription; + + Array = array; + } + + /// + /// Removes a subscription to the from the collection. + /// + internal void Remove(TsCAeSubscription subscription) + { + var array = new TsCAeSubscription[Count - 1]; + + var index = 0; + + for (var ii = 0; ii < Array.Length; ii++) + { + var element = (TsCAeSubscription)Array.GetValue(ii); + + if (subscription != element) + { + array[index++] = element; + } + } + + Array = array; + } + #endregion + } + #endregion + + #region Names Class + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Count = "CT"; + internal const string Subscription = "SU"; + } + #endregion + + #region Fields + private int filters_; + private bool disposing_; + private SubscriptionCollection subscriptions_ = new SubscriptionCollection(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// 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 TsCAeServer(OpcFactory factory, OpcUrl url) + : base(factory, url) + { + } + + /// + /// Constructs a server by de-serializing its OpcUrl from the stream. + /// + protected TsCAeServer(SerializationInfo info, StreamingContext context) + : + base(info, context) + { + var count = (int)info.GetValue(Names.Count, typeof(int)); + + subscriptions_ = new SubscriptionCollection(); + + for (var ii = 0; ii < count; ii++) + { + var subscription = (TsCAeSubscription)info.GetValue(Names.Subscription + ii, typeof(TsCAeSubscription)); + subscriptions_.Add(subscription); + } + } + #endregion + + #region Properties + /// + /// The filters supported by the server. + /// + // ReSharper disable once UnusedMember.Global + public int AvailableFilters => filters_; + + /// + /// The outstanding subscriptions for the server. + /// + public SubscriptionCollection Subscriptions => subscriptions_; + #endregion + + #region Public Methods + /// + /// Connects to the server with the specified OpcUrl and credentials. + /// + public override void Connect(OpcUrl url, OpcConnectData connectData) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + // connect to server. + base.Connect(url, connectData); + + // all done if no subscriptions. + if (subscriptions_.Count == 0) + { + return; + } + + // create subscriptions (should only happen if server has been deserialized). + var subscriptions = new SubscriptionCollection(); + + foreach (TsCAeSubscription template in subscriptions_) + { + // create subscription for template. + try { subscriptions.Add(EstablishSubscription(template)); } + catch + { + // ignored + } + } + + // 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. + disposing_ = true; + + foreach (TsCAeSubscription subscription in subscriptions_) + { + subscription.Dispose(); + } + + disposing_ = false; + + // disconnect from server. + base.Disconnect(); + } + + /// + /// Returns the current server status. + /// + /// The current server status. + public OpcServerStatus GetServerStatus() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var status = ((ITsCAeServer)Server).GetServerStatus(); + + if (status != null) + { + if (status.StatusInfo == null) + { + status.StatusInfo = GetString($"serverState.{status.ServerState}"); + } + } + else + { + if (Server == null) throw new NotConnectedException(); + } + + return status; + } + + /// + /// Creates a new event subscription. + /// + /// The initial state for the subscription. + /// The new subscription object. + public ITsCAeSubscription CreateSubscription(TsCAeSubscriptionState state) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + // create remote object. + var subscription = ((ITsCAeServer)Server).CreateSubscription(state); + + if (subscription != null) + { + // create wrapper. + var wrapper = new TsCAeSubscription(this, subscription, state); + subscriptions_.Add(wrapper); + return wrapper; + } + + // should never happen. + return null; + } + + /// + /// Returns the event filters supported by the server. + /// + /// A bit mask of all event filters supported by the server. + public int QueryAvailableFilters() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + filters_ = ((ITsCAeServer)Server).QueryAvailableFilters(); + + return filters_; + } + + /// + /// Returns the event categories supported by the server for the specified event types. + /// + /// A bit mask for the event types of interest. + /// A collection of event categories. + public TsCAeCategory[] QueryEventCategories(int eventType) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + // fetch categories from server. + var categories = ((ITsCAeServer)Server).QueryEventCategories(eventType); + + // return result. + return categories; + } + + /// + /// Returns the condition names supported by the server for the specified event categories. + /// + /// A bit mask for the event categories of interest. + /// A list of condition names. + public string[] QueryConditionNames(int eventCategory) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + // fetch condition names from the server. + var conditions = ((ITsCAeServer)Server).QueryConditionNames(eventCategory); + + // return result. + return conditions; + } + + /// + /// Returns the sub-condition names supported by the server for the specified event condition. + /// + /// The name of the condition. + /// A list of sub-condition names. + public string[] QuerySubConditionNames(string conditionName) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + // fetch sub-condition names from the server. + var subConditions = ((ITsCAeServer)Server).QuerySubConditionNames(conditionName); + + // return result. + return subConditions; + } + + /// + /// Returns the condition names supported by the server for the specified event source. + /// + /// The name of the event source. + /// A list of condition names. + public string[] QueryConditionNames(string sourceName) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + // fetch condition names from the server. + var conditions = ((ITsCAeServer)Server).QueryConditionNames(sourceName); + + // return result. + return conditions; + } + + /// + /// Returns the event attributes supported by the server for the specified event categories. + /// + /// A bit mask for the event categories of interest. + /// A collection of event attributes. + public TsCAeAttribute[] QueryEventAttributes(int eventCategory) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + // fetch attributes from server. + var attributes = ((ITsCAeServer)Server).QueryEventAttributes(eventCategory); + + // return result. + return attributes; + } + + /// + /// Returns the DA item ids for a set of attribute ids belonging to events which meet the specified filter criteria. + /// + /// The event source of interest. + /// The id of the event category for the events of interest. + /// The name of a condition within the event category. + /// The name of a sub-condition within a multi-state condition. + /// The ids of the attributes to return item ids for. + /// A list of item urls for each specified attribute. + public TsCAeItemUrl[] TranslateToItemIDs( + string sourceName, + int eventCategory, + string conditionName, + string subConditionName, + int[] attributeIDs) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var itemUrls = ((ITsCAeServer)Server).TranslateToItemIDs( + sourceName, + eventCategory, + conditionName, + subConditionName, + attributeIDs); + + return itemUrls; + } + + /// + /// Returns the current state information for the condition instance corresponding to the source and condition name. + /// + /// The source name + /// A condition name for the source. + /// The list of attributes to return with the condition state. + /// The current state of the connection. + public TsCAeCondition GetConditionState( + string sourceName, + string conditionName, + int[] attributeIDs) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var condition = ((ITsCAeServer)Server).GetConditionState(sourceName, conditionName, attributeIDs); + + return condition; + } + + /// + /// Places the specified process areas into the enabled state. + /// + /// A list of fully qualified area names. + /// The results of the operation for each area. + public OpcResult[] EnableConditionByArea(string[] areas) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCAeServer)Server).EnableConditionByArea(areas); + + return results; + } + + /// + /// Places the specified process areas into the disabled state. + /// + /// A list of fully qualified area names. + /// The results of the operation for each area. + public OpcResult[] DisableConditionByArea(string[] areas) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCAeServer)Server).DisableConditionByArea(areas); + + return results; + } + + /// + /// Places the specified process areas into the enabled state. + /// + /// A list of fully qualified source names. + /// The results of the operation for each area. + public OpcResult[] EnableConditionBySource(string[] sources) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCAeServer)Server).EnableConditionBySource(sources); + + return results; + } + + /// + /// Places the specified process areas into the disabled state. + /// + /// A list of fully qualified source names. + /// The results of the operation for each area. + public OpcResult[] DisableConditionBySource(string[] sources) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCAeServer)Server).DisableConditionBySource(sources); + + return results; + } + + /// + /// Returns the enabled state for the specified process areas. + /// + /// A list of fully qualified area names. + public TsCAeEnabledStateResult[] GetEnableStateByArea(string[] areas) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCAeServer)Server).GetEnableStateByArea(areas); + + return results; + } + + /// + /// Returns the enabled state for the specified event sources. + /// + /// A list of fully qualified source names. + public TsCAeEnabledStateResult[] GetEnableStateBySource(string[] sources) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCAeServer)Server).GetEnableStateBySource(sources); + + return results; + } + + /// + /// Used to acknowledge one or more conditions in the event server. + /// + /// The identifier for who is acknowledging the condition. + /// A comment associated with the acknowledgment. + /// The conditions being acknowledged. + /// A list of result id indicating whether each condition was successfully acknowledged. + public OpcResult[] AcknowledgeCondition( + string acknowledgmentId, + string comment, + TsCAeEventAcknowledgement[] conditions) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + return ((ITsCAeServer)Server).AcknowledgeCondition(acknowledgmentId, comment, conditions); + } + + /// + /// Browses for all children of the specified area that meet the filter criteria. + /// + /// The full-qualified id for the area. + /// The type of children to return. + /// The expression used to filter the names of children returned. + /// The set of elements that meet the filter criteria. + public TsCAeBrowseElement[] Browse( + string areaId, + TsCAeBrowseType browseType, + string browseFilter) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + return ((ITsCAeServer)Server).Browse(areaId, browseType, browseFilter); + } + + /// + /// Browses for all children of the specified area that meet the filter criteria. + /// + /// The full-qualified id for the area. + /// The type of children to return. + /// The expression used to filter the names of children returned. + /// The maximum number of elements to return. + /// The object used to continue the browse if the number nodes exceeds the maximum specified. + /// The set of elements that meet the filter criteria. + public TsCAeBrowseElement[] Browse( + string areaId, + TsCAeBrowseType browseType, + string browseFilter, + int maxElements, + out IOpcBrowsePosition position) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + return ((ITsCAeServer)Server).Browse(areaId, browseType, browseFilter, maxElements, out position); + } + + /// + /// Continues browsing the server's address space at the specified position. + /// + /// The maximum number of elements to return. + /// The position object used to continue a browse operation. + /// The set of elements that meet the filter criteria. + public TsCAeBrowseElement[] BrowseNext(int maxElements, ref IOpcBrowsePosition position) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (Server == null) throw new NotConnectedException(); + + return ((ITsCAeServer)Server).BrowseNext(maxElements, ref position); + } + + #endregion + + #region ISerializable Members + + /// + /// Serializes a server into a stream. + /// + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + + info.AddValue(Names.Count, subscriptions_.Count); + + for (var ii = 0; ii < subscriptions_.Count; ii++) + { + info.AddValue(Names.Subscription + ii, subscriptions_[ii]); + } + } + + #endregion + + #region Private Methods + + /// + /// Called when a subscription object is disposed. + /// + /// + internal void SubscriptionDisposed(TsCAeSubscription subscription) + { + if (!disposing_) + { + subscriptions_.Remove(subscription); + } + } + + /// + /// Establishes a subscription based on the template provided. + /// + private TsCAeSubscription EstablishSubscription(TsCAeSubscription template) + { + ITsCAeSubscription remoteServer = null; + + try + { + // create remote object. + remoteServer = ((ITsCAeServer)Server).CreateSubscription(template.State); + + if (remoteServer == null) + { + return null; + } + + // create wrapper. + var subscription = new TsCAeSubscription(this, remoteServer, template.State); + + // set filters. + subscription.SetFilters(template.Filters); + + // set attributes. + var enumerator = template.Attributes.GetEnumerator(); + + while (enumerator.MoveNext()) + { + if (enumerator.Key != null) + subscription.SelectReturnedAttributes( + (int)enumerator.Key, + ((TsCAeSubscription.AttributeCollection)enumerator.Value).ToArray()); + } + + // return new subscription + return subscription; + } + catch + { + if (remoteServer != null) + { + remoteServer.Dispose(); + } + } + + // return null. + return null; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/ServerState.cs b/Technosoftware/DaAeHdaClient/Ae/ServerState.cs new file mode 100644 index 0000000..490ee13 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/ServerState.cs @@ -0,0 +1,68 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Current Status of an OPC AE server + /// + public enum TsCAeServerState + { + /// + /// The server state is not known. + /// + Unknown, + + /// + /// The server is running normally. This is the usual state for a server + /// + Running, + + /// + /// A vendor specific fatal error has occurred within the server. The server is no longer functioning. The recovery procedure from this situation is vendor specific. An error code of E_FAIL should generally be returned from any other server method. + /// + Failed, + + /// + /// The server is running but has no configuration information loaded and thus cannot function normally. Note this state implies that the server needs configuration information in order to function. Servers which do not require configuration information should not return this state. + /// + NoConfig, + + /// + /// The server has been temporarily suspended via some vendor specific method and is not getting or sending data. Note that Quality will be returned as OPC_QUALITY_OUT_OF_SERVICE. + /// + Suspended, + + /// + /// The server is in Test Mode. The outputs are disconnected from the real hardware but the server will otherwise behave normally. Inputs may be real or may be simulated depending on the vendor implementation. Quality will generally be returned normally. + /// + Test, + + /// + /// The server is in Test Mode. The outputs are disconnected from the real hardware but the server will otherwise behave normally. Inputs may be real or may be simulated depending on the vendor implementation. Quality will generally be returned normally. + /// + CommFault + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/StateMask.cs b/Technosoftware/DaAeHdaClient/Ae/StateMask.cs new file mode 100644 index 0000000..dba0f4c --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/StateMask.cs @@ -0,0 +1,70 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Defines masks to be used when modifying the subscription or item state. + /// + [Flags] + public enum TsCAeStateMask + { + /// + /// A name assigned to subscription. + /// + Name = 0x0001, + + /// + /// The client assigned handle for the item or subscription. + /// + ClientHandle = 0x0002, + + /// + /// Whether the subscription is active. + /// + Active = 0x0004, + + /// + /// The maximum rate at which the server send event notifications. + /// + BufferTime = 0x0008, + + /// + /// The requested maximum number of events that will be sent in a single callback. + /// + MaxSize = 0x0010, + + /// + /// The maximum period between updates sent to the client. + /// + KeepAlive = 0x0020, + + /// + /// All fields are valid. + /// + All = 0xFFFF + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/SubCondition.cs b/Technosoftware/DaAeHdaClient/Ae/SubCondition.cs new file mode 100644 index 0000000..3f99a5e --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/SubCondition.cs @@ -0,0 +1,83 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// The description of an item sub-condition supported by the server. + /// + [Serializable] + public class TsCAeSubCondition : ICloneable + { + #region Fields + private int severity_ = 1; + #endregion + + #region Properties + /// + /// The name assigned to the sub-condition. + /// + public string Name { get; set; } + + /// + /// A server-specific expression which defines the sub-state represented by the sub-condition. + /// + public string Definition { get; set; } + + /// + /// The severity of any event notifications generated on behalf of this sub-condition. + /// + public int Severity + { + get => severity_; + set => severity_ = value; + } + + /// + /// The text string to be included in any event notification generated on behalf of this sub-condition. + /// + public string Description { get; set; } + #endregion + + #region Helper Methods + /// + /// Returns a string that represents the current object. + /// + /// + public override string ToString() + { + return Name; + } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() { return MemberwiseClone(); } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/Subscription.cs b/Technosoftware/DaAeHdaClient/Ae/Subscription.cs new file mode 100644 index 0000000..fd6bdef --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/Subscription.cs @@ -0,0 +1,610 @@ +#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; +using System.Runtime.Serialization; + +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// An in-process object which provides access to AE subscription objects. + /// + [Serializable] + public class TsCAeSubscription : ITsCAeSubscription, ISerializable, ICloneable + { + #region CategoryCollection Class + /// + /// Contains a read-only collection category ids. + /// + public class CategoryCollection : OpcReadOnlyCollection + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal CategoryCollection() : base(new int[0]) { } + + /// + /// Creates a collection containing the list of category ids. + /// + internal CategoryCollection(int[] categoryIDs) : base(categoryIDs) { } + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new int this[int index] => (int)Array.GetValue(index); + + /// + /// Returns a copy of the collection as an array. + /// + public new int[] ToArray() + { + return (int[])OpcConvert.Clone(Array); + } + #endregion + } + #endregion + + #region StringCollection Class + /// + /// Contains a read-only collection of strings. + /// + public class StringCollection : OpcReadOnlyCollection + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal StringCollection() : base(new string[0]) { } + + /// + /// Creates a collection containing the specified strings. + /// + internal StringCollection(string[] strings) : base(strings) { } + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new string this[int index] => (string)Array.GetValue(index); + + /// + /// Returns a copy of the collection as an array. + /// + public new string[] ToArray() + { + return (string[])OpcConvert.Clone(Array); + } + #endregion + } + #endregion + + #region AttributeDictionary Class + /// + /// Contains a read-only dictionary of attribute lists indexed by category id. + /// + [Serializable] + public class AttributeDictionary : OpcReadOnlyDictionary + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal AttributeDictionary() : base(null) { } + + /// + /// Constructs an dictionary from a set of category ids. + /// + internal AttributeDictionary(Hashtable dictionary) : base(dictionary) { } + #endregion + + #region Public Methods + /// + /// Gets or sets the attribute collection for the specified category. + /// + public AttributeCollection this[int categoryId] => (AttributeCollection)base[categoryId]; + + /// + /// Adds or replaces the set of attributes associated with the category. + /// + internal void Update(int categoryId, int[] attributeIDs) + { + Dictionary[categoryId] = new AttributeCollection(attributeIDs); + } + #endregion + + #region ISerializable Members + /// + /// Constructs an object by deserializing it from a stream. + /// + protected AttributeDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } + #endregion + } + #endregion + + #region AttributeCollection Class + /// + /// Contains a read-only collection attribute ids. + /// + [Serializable] + public class AttributeCollection : OpcReadOnlyCollection + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal AttributeCollection() : base(new int[0]) { } + + /// + /// Creates a collection containing the specified attribute ids. + /// + internal AttributeCollection(int[] attributeIDs) : base(attributeIDs) { } + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new int this[int index] => (int)Array.GetValue(index); + + /// + /// Returns a copy of the collection as an array. + /// + public new int[] ToArray() + { + return (int[])OpcConvert.Clone(Array); + } + #endregion + + #region ISerializable Members + /// + /// Constructs an object by deserializing it from a stream. + /// + protected AttributeCollection(SerializationInfo info, StreamingContext context) : base(info, context) { } + #endregion + } + #endregion + + #region Names Class + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string State = "ST"; + internal const string Filters = "FT"; + internal const string Attributes = "AT"; + } + #endregion + + #region Fields + private bool disposed_; + private TsCAeServer server_; + private ITsCAeSubscription subscription_; + + // state + private TsCAeSubscriptionState state_; + private string name_; + + // filters + private TsCAeSubscriptionFilters subscriptionFilters_ = new TsCAeSubscriptionFilters(); + private CategoryCollection categories_ = new CategoryCollection(); + private StringCollection areas_ = new StringCollection(); + private StringCollection sources_ = new StringCollection(); + + // returned attributes + private AttributeDictionary attributes_ = new AttributeDictionary(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with default values. + /// + public TsCAeSubscription(TsCAeServer server, ITsCAeSubscription subscription, TsCAeSubscriptionState state) + { + server_ = server ?? throw new ArgumentNullException(nameof(server)); + subscription_ = subscription ?? throw new ArgumentNullException(nameof(subscription)); + state_ = (TsCAeSubscriptionState)state.Clone(); + name_ = state.Name; + } + + /// + /// The finalizer implementation. + /// + ~TsCAeSubscription() + { + Dispose(false); + } + + /// + /// + /// + public virtual void Dispose() + { + Dispose(true); + // Take yourself off the Finalization queue + // to prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if(!disposed_) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if(disposing) + { + if (subscription_ != null) + { + server_.SubscriptionDisposed(this); + subscription_.Dispose(); + } + } + // Release unmanaged resources. If disposing is false, + // only the following code is executed. + } + disposed_ = true; + } + + #endregion + + #region Properties + + /// + /// The server that the subscription object belongs to. + /// + public TsCAeServer Server => server_; + + /// + /// A descriptive name for the subscription. + /// + public string Name => state_.Name; + + /// + /// A unique identifier for the subscription assigned by the client. + /// + public object ClientHandle => state_.ClientHandle; + + /// + /// Whether the subscription is monitoring for events to send to the client. + /// + public bool Active => state_.Active; + + /// + /// The maximum rate at which the server send event notifications. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public int BufferTime => state_.BufferTime; + + /// + /// The requested maximum number of events that will be sent in a single callback. + /// + public int MaxSize => state_.MaxSize; + + /// + /// The maximum period between updates sent to the client. + /// + public int KeepAlive => state_.KeepAlive; + + /// + /// A mask indicating which event types should be sent to the client. + /// + public int EventTypes => subscriptionFilters_.EventTypes; + + /// + /// The highest severity for the events that should be sent to the client. + /// + // ReSharper disable once UnusedMember.Global + public int HighSeverity => subscriptionFilters_.HighSeverity; + + /// + /// The lowest severity for the events that should be sent to the client. + /// + // ReSharper disable once UnusedMember.Global + public int LowSeverity => subscriptionFilters_.LowSeverity; + + /// + /// The event category ids monitored by this subscription. + /// + public CategoryCollection Categories => categories_; + + /// + /// A list of full-qualified ids for process areas of interest - only events or conditions in these areas will be reported. + /// + public StringCollection Areas => areas_; + + /// + /// A list of full-qualified ids for sources of interest - only events or conditions from these sources will be reported. + /// + public StringCollection Sources => sources_; + + /// + /// The list of attributes returned for each event category. + /// + public AttributeDictionary Attributes => attributes_; + #endregion + + #region Public Methods + /// + /// Returns a writable copy of the current attributes. + /// + public TsCAeAttributeDictionary GetAttributes() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + var attributes = new TsCAeAttributeDictionary(); + + var enumerator = attributes_.GetEnumerator(); + + while (enumerator.MoveNext()) + { + if (enumerator.Key != null) + { + var categoryId = (int)enumerator.Key; + var attributeIDs = (AttributeCollection)enumerator.Value; + + attributes.Add(categoryId, attributeIDs.ToArray()); + } + } + + return attributes; + } + #endregion + + #region ISerializable Members + /// + /// Constructs a server by de-serializing its OpcUrl from the stream. + /// + protected TsCAeSubscription(SerializationInfo info, StreamingContext context) + { + state_ = (TsCAeSubscriptionState)info.GetValue(Names.State, typeof(TsCAeSubscriptionState)); + subscriptionFilters_ = (TsCAeSubscriptionFilters)info.GetValue(Names.Filters, typeof(TsCAeSubscriptionFilters)); + attributes_ = (AttributeDictionary)info.GetValue(Names.Attributes, typeof(AttributeDictionary)); + + name_ = state_.Name; + + categories_ = new CategoryCollection(subscriptionFilters_.Categories.ToArray()); + areas_ = new StringCollection(subscriptionFilters_.Areas.ToArray()); + sources_ = new StringCollection(subscriptionFilters_.Sources.ToArray()); + } + + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.State, state_); + info.AddValue(Names.Filters, subscriptionFilters_); + info.AddValue(Names.Attributes, attributes_); + } + #endregion + + #region ICloneable Members + /// + /// Returns an unconnected copy of the subscription with the same items. + /// + public virtual object Clone() + { + // do a memberwise clone. + var clone = (TsCAeSubscription)MemberwiseClone(); + + /* + // place clone in disconnected state. + clone.server = null; + clone.subscription = null; + clone.state = (SubscriptionState)state.Clone(); + + // clear server handles. + clone.state.ServerHandle = null; + + // always make cloned subscriptions inactive. + clone.state.Active = false; + + // clone items. + if (clone.items != null) + { + ArrayList items = new ArrayList(); + + foreach (Item item in clone.items) + { + items.Add(item.Clone()); + } + + clone.items = (Item[])items.ToArray(typeof(Item)); + } + */ + + // return clone. + return clone; + } + #endregion + + #region ISubscription Members + /// + /// An event to receive data change updates. + /// + public event TsCAeDataChangedEventHandler DataChangedEvent + { + add => subscription_.DataChangedEvent += value; + remove => subscription_.DataChangedEvent -= value; + } + + /// + /// Returns the current state of the subscription. + /// + /// The current state of the subscription. + public TsCAeSubscriptionState GetState() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + state_ = subscription_.GetState(); + state_.Name = name_; + + return (TsCAeSubscriptionState)state_.Clone(); + } + + /// + /// Changes the state of a subscription. + /// + /// A bit mask that indicates which elements of the subscription state are changing. + /// The new subscription state. + /// The actual subscription state after applying the changes. + public TsCAeSubscriptionState ModifyState(int masks, TsCAeSubscriptionState state) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + state_ = subscription_.ModifyState(masks, state); + + if ((masks & (int)TsCAeStateMask.Name) != 0) + { + state_.Name = name_ = state.Name; + } + else + { + state_.Name = name_; + } + + return (TsCAeSubscriptionState)state_.Clone(); + } + + /// + /// Returns the current filters for the subscription. + /// + /// The current filters for the subscription. + public TsCAeSubscriptionFilters GetFilters() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + subscriptionFilters_ = subscription_.GetFilters(); + categories_ = new CategoryCollection(subscriptionFilters_.Categories.ToArray()); + areas_ = new StringCollection(subscriptionFilters_.Areas.ToArray()); + sources_ = new StringCollection(subscriptionFilters_.Sources.ToArray()); + + return (TsCAeSubscriptionFilters)subscriptionFilters_.Clone(); + } + + /// + /// Sets the current filters for the subscription. + /// + /// The new filters to use for the subscription. + public void SetFilters(TsCAeSubscriptionFilters filters) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + subscription_.SetFilters(filters); + + GetFilters(); + } + + /// + /// Returns the set of attributes to return with event notifications. + /// + /// The set of attributes to returned with event notifications. + public int[] GetReturnedAttributes(int eventCategory) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + var attributeIDs = subscription_.GetReturnedAttributes(eventCategory); + + attributes_.Update(eventCategory, (int[])OpcConvert.Clone(attributeIDs)); + + return attributeIDs; + } + + /// + /// Selects the set of attributes to return with event notifications. + /// + /// The specific event category for which the attributes apply. + /// The list of attribute ids to return. + public void SelectReturnedAttributes(int eventCategory, int[] attributeIDs) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + subscription_.SelectReturnedAttributes(eventCategory, attributeIDs); + + attributes_.Update(eventCategory, (int[])OpcConvert.Clone(attributeIDs)); + } + + /// + /// Force a refresh for all active conditions and inactive, unacknowledged conditions whose event notifications match the filter of the event subscription. + /// + public void Refresh() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + subscription_.Refresh(); + } + + /// + /// Cancels an outstanding refresh request. + /// + public void CancelRefresh() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.AlarmsConditions); + if (subscription_ == null) throw new NotConnectedException(); + + subscription_.CancelRefresh(); + } + #endregion + + #region Internal Properties + /// + /// The current state. + /// + internal TsCAeSubscriptionState State => state_; + + /// + /// The current filters. + /// + internal TsCAeSubscriptionFilters Filters => subscriptionFilters_; + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/SubscriptionFilters.cs b/Technosoftware/DaAeHdaClient/Ae/SubscriptionFilters.cs new file mode 100644 index 0000000..40d52aa --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/SubscriptionFilters.cs @@ -0,0 +1,232 @@ +#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.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Describes the event filters for a subscription. + /// + [Serializable] + public class TsCAeSubscriptionFilters : ICloneable, ISerializable + { + #region CategoryCollection Class + /// + /// Contains a writable collection category ids. + /// + [Serializable] + public class CategoryCollection : OpcWriteableCollection + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal CategoryCollection() : base(null, typeof(int)) { } + + #region ISerializable Members + /// + /// Constructs an object by deserializing it from a stream. + /// + protected CategoryCollection(SerializationInfo info, StreamingContext context) : base(info, context) { } + #endregion + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new int this[int index] => (int)Array[index]; + + /// + /// Returns a copy of the collection as an array. + /// + public new int[] ToArray() + { + return (int[])Array.ToArray(typeof(int)); + } + #endregion + } + #endregion + + #region StringCollection Class + /// + /// Contains a writable collection of strings. + /// + [Serializable] + public class StringCollection : OpcWriteableCollection + { + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + internal StringCollection() : base(null, typeof(string)) { } + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public new string this[int index] => (string)Array[index]; + + /// + /// Returns a copy of the collection as an array. + /// + public new string[] ToArray() + { + return (string[])Array.ToArray(typeof(string)); + } + #endregion + + #region ISerializable Members + /// + /// Constructs an object by deserializing it from a stream. + /// + protected StringCollection(SerializationInfo info, StreamingContext context) : base(info, context) { } + #endregion + + } + #endregion + + #region Names Class + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string EventTypes = "ET"; + internal const string Categories = "CT"; + internal const string HighSeverity = "HS"; + internal const string LowSeverity = "LS"; + internal const string Areas = "AR"; + internal const string Sources = "SR"; + } + #endregion + + #region Fields + private int eventTypes_ = (int)TsCAeEventType.All; + private CategoryCollection categories_ = new CategoryCollection(); + private int highSeverity_ = 1000; + private int lowSeverity_ = 1; + private StringCollection areas_ = new StringCollection(); + private StringCollection sources_ = new StringCollection(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with default values. + /// + public TsCAeSubscriptionFilters() { } + + /// + /// Constructs a server by de-serializing its OpcUrl from the stream. + /// + protected TsCAeSubscriptionFilters(SerializationInfo info, StreamingContext context) + { + eventTypes_ = (int)info.GetValue(Names.EventTypes, typeof(int)); + categories_ = (CategoryCollection)info.GetValue(Names.Categories, typeof(CategoryCollection)); + highSeverity_ = (int)info.GetValue(Names.HighSeverity, typeof(int)); + lowSeverity_ = (int)info.GetValue(Names.LowSeverity, typeof(int)); + areas_ = (StringCollection)info.GetValue(Names.Areas, typeof(StringCollection)); + sources_ = (StringCollection)info.GetValue(Names.Sources, typeof(StringCollection)); + } + #endregion + + #region Properties + /// + /// A mask indicating which event types should be sent to the client. + /// + public int EventTypes + { + get => eventTypes_; + set => eventTypes_ = value; + } + + /// + /// The highest severity for the events that should be sent to the client. + /// + public int HighSeverity + { + get => highSeverity_; + set => highSeverity_ = value; + } + + /// + /// The lowest severity for the events that should be sent to the client. + /// + public int LowSeverity + { + get => lowSeverity_; + set => lowSeverity_ = value; + } + + /// + /// The category ids for the events that should be sent to the client. + /// + public CategoryCollection Categories => categories_; + + /// + /// A list of full-qualified ids for process areas of interest - only events or conditions in these areas will be reported. + /// + public StringCollection Areas => areas_; + + /// + /// A list of full-qualified ids for sources of interest - only events or conditions from these sources will be reported. + /// + public StringCollection Sources => sources_; + #endregion + + #region ISerializable Members + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.EventTypes, eventTypes_); + info.AddValue(Names.Categories, categories_); + info.AddValue(Names.HighSeverity, highSeverity_); + info.AddValue(Names.LowSeverity, lowSeverity_); + info.AddValue(Names.Areas, areas_); + info.AddValue(Names.Sources, sources_); + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var filters = (TsCAeSubscriptionFilters)MemberwiseClone(); + + filters.categories_ = (CategoryCollection)categories_.Clone(); + filters.areas_ = (StringCollection)areas_.Clone(); + filters.sources_ = (StringCollection)sources_.Clone(); + + return filters; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Ae/SubscriptionState.cs b/Technosoftware/DaAeHdaClient/Ae/SubscriptionState.cs new file mode 100644 index 0000000..36f7ad3 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Ae/SubscriptionState.cs @@ -0,0 +1,88 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Ae +{ + /// + /// Describes the state of a subscription. + /// + [Serializable] + public class TsCAeSubscriptionState : ICloneable + { + #region Fields + private bool active_ = true; + #endregion + + #region Constructors, Destructor, Initialization + #endregion + + #region Properties + /// + /// A descriptive name for the subscription. + /// + public string Name { get; set; } + + /// + /// A unique identifier for the subscription assigned by the client. + /// + public object ClientHandle { get; set; } + + /// + /// Whether the subscription is monitoring for events to send to the client. + /// + public bool Active + { + get => active_; + set => active_ = value; + } + + /// + /// The maximum rate at which the server send event notifications. + /// + public int BufferTime { get; set; } + + /// + /// The requested maximum number of events that will be sent in a single callback. + /// + public int MaxSize { get; set; } + + /// + /// The maximum period between updates sent to the client. + /// + public int KeepAlive { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/ApplicationInstance.cs b/Technosoftware/DaAeHdaClient/ApplicationInstance.cs new file mode 100644 index 0000000..2b83e70 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/ApplicationInstance.cs @@ -0,0 +1,66 @@ +#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 Technosoftware.DaAeHdaClient.Utilities; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Manages the license to enable the different product versions. + /// + public partial class ApplicationInstance + { + #region Properties + /// + /// This flag suppresses the conversion to local time done during marshalling. + /// + public static bool TimeAsUtc { get; set; } + #endregion + + #region Public Methods + /// + /// Gets the log file directory and ensures it is writable. + /// + public static string GetLogFileDirectory() + { + return ConfigUtils.GetLogFileDirectory(); + } + + /// + /// Enable the trace. + /// + /// The path to use. + /// The filename. + public static void EnableTrace(string path, string filename) + { + ConfigUtils.EnableTrace(path, filename); + } + #endregion + + #region Internal Fields + internal static bool InitializeSecurityCalled = false; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/ComplexItem.cs b/Technosoftware/DaAeHdaClient/Cpx/ComplexItem.cs new file mode 100644 index 0000000..20cb7c7 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/ComplexItem.cs @@ -0,0 +1,715 @@ +#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; +using System.Xml; +using System.IO; + +using Technosoftware.DaAeHdaClient.Da; +#endregion + +namespace Technosoftware.DaAeHdaClient.Cpx +{ + /// + /// A class that contains complex data related properties for an item. + /// + public class TsCCpxComplexItem : OpcItem + { + /////////////////////////////////////////////////////////////////////// + #region Constants + + /// + /// The reserved name for complex data branch in the server namespace. + /// + public const string CPX_BRANCH = "CPX"; + + /// + /// The reserved name for the data filters branch in the CPX namespace. + /// + public const string CPX_DATA_FILTERS = "DataFilters"; + + /// + /// The set of all complex data item properties. + /// + public static readonly TsDaPropertyID[] CPX_PROPERTIES = new TsDaPropertyID[] + { + TsDaProperty.TYPE_SYSTEM_ID, + TsDaProperty.DICTIONARY_ID, + TsDaProperty.TYPE_ID, + TsDaProperty.UNCONVERTED_ITEM_ID, + TsDaProperty.UNFILTERED_ITEM_ID, + TsDaProperty.DATA_FILTER_VALUE + }; + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Fields + + private string _typeSystemID; + private string _dictionaryID; + private string _typeID; + private OpcItem _dictionaryItemID; + private OpcItem _typeItemID; + private OpcItem _unconvertedItemID; + private OpcItem _unfilteredItemID; + private OpcItem _filterItem; + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Constructors, Destructor, Initialization + + /// + /// Initializes the object with the default values. + /// + public TsCCpxComplexItem() { } + + /// + /// Initializes the object from an item identifier. + /// + /// The item id. + public TsCCpxComplexItem(OpcItem itemID) + { + ItemPath = itemID.ItemPath; + ItemName = itemID.ItemName; + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Properties + + /// + /// The name of the item in the server address space. + /// + public string Name { get; private set; } + + /// + /// The type system id for the complex item. + /// + public string TypeSystemID => _typeSystemID; + + /// + /// The dictionary id for the complex item. + /// + public string DictionaryID => _dictionaryID; + + /// + /// The type id for the complex item. + /// + public string TypeID => _typeID; + + /// + /// The id of the item containing the dictionary for the item. + /// + public OpcItem DictionaryItemID => _dictionaryItemID; + + /// + /// The id of the item containing the type description for the item. + /// + public OpcItem TypeItemID => _typeItemID; + + /// + /// The id of the unconverted version of the item. Only valid for items which apply type conversions to the item. + /// + public OpcItem UnconvertedItemID => _unconvertedItemID; + + /// + /// The id of the unfiltered version of the item. Only valid for items apply data filters to the item. + /// + public OpcItem UnfilteredItemID => _unfilteredItemID; + + /// + /// The item used to create new data filters for the complex data item (null is item does not support it). + /// + public OpcItem DataFilterItem => _filterItem; + + /// + /// The current data filter value. Only valid for items apply data filters to the item. + /// + public string DataFilterValue { get; set; } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Public Methods + + /// + /// Returns an appropriate string representation of the object. + /// + public override string ToString() + { + if (Name != null || Name.Length != 0) + { + return Name; + } + + return ItemName; + } + + /// + /// Returns the root complex data item for the object. + /// + public TsCCpxComplexItem GetRootItem() + { + if (_unconvertedItemID != null) + { + return TsCCpxComplexTypeCache.GetComplexItem(_unconvertedItemID); + } + + if (_unfilteredItemID != null) + { + return TsCCpxComplexTypeCache.GetComplexItem(_unfilteredItemID); + } + + return this; + } + + /// + /// Reads the current complex data item properties from the server. + /// + /// The server object + public void Update(TsCDaServer server) + { + // clear the existing state. + Clear(); + + // check if the item supports any of the complex data properties. + var results = server.GetProperties( + new OpcItem[] { this }, + CPX_PROPERTIES, + true); + + // unexpected return value. + if (results == null || results.Length != 1) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "Unexpected results returned from server."); + } + + // update object. + if (!Init((TsCDaItemProperty[])results[0].ToArray(typeof(TsCDaItemProperty)))) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_INVALIDARG.Code, OpcResult.FuncCallType.SysFuncCall, null), "Not a valid complex item."); + } + + // check if data filters are suppported for the item. + GetDataFilterItem(server); + } + + /// + /// Fetches the set of type conversions from the server. + /// + /// The server object + public TsCCpxComplexItem[] GetTypeConversions(TsCDaServer server) + { + // only the root item can have type conversions. + if (_unconvertedItemID != null || _unfilteredItemID != null) + { + return null; + } + + TsCDaBrowsePosition position = null; + + try + { + // look for the 'CPX' branch. + var filters = new TsCDaBrowseFilters { ElementNameFilter = CPX_BRANCH, BrowseFilter = TsCDaBrowseFilter.Branch, ReturnAllProperties = false, PropertyIDs = null, ReturnPropertyValues = false }; + + var elements = server.Browse(this, filters, out position); + + // nothing found. + if (elements == null || elements.Length == 0) + { + return null; + } + + // release the browse position object. + if (position != null) + { + position.Dispose(); + position = null; + } + + // browse for type conversions. + var itemID = new OpcItem(elements[0].ItemPath, elements[0].ItemName); + + filters.ElementNameFilter = null; + filters.BrowseFilter = TsCDaBrowseFilter.Item; + filters.ReturnAllProperties = false; + filters.PropertyIDs = CPX_PROPERTIES; + filters.ReturnPropertyValues = true; + + elements = server.Browse(itemID, filters, out position); + + // nothing found. + if (elements == null || elements.Length == 0) + { + return new TsCCpxComplexItem[0]; + } + + // contruct an array of complex data items for each available conversion. + var conversions = new ArrayList(elements.Length); + + Array.ForEach(elements, element => + { + if (element.Name != CPX_DATA_FILTERS) + { + var item = new TsCCpxComplexItem(); + if (item.Init(element)) + { + // check if data filters supported for type conversion. + item.GetDataFilterItem(server); + conversions.Add(item); + } + } + }); + + // return the set of available conversions. + return (TsCCpxComplexItem[])conversions.ToArray(typeof(TsCCpxComplexItem)); + } + finally + { + if (position != null) + { + position.Dispose(); + position = null; + } + } + } + + /// + /// Fetches the set of data filters from the server. + /// + /// The server object + public TsCCpxComplexItem[] GetDataFilters(TsCDaServer server) + { + // not a valid operation for data filter items. + if (_unfilteredItemID != null) + { + return null; + } + + // data filters not supported by the item. + if (_filterItem == null) + { + return null; + } + + TsCDaBrowsePosition position = null; + + try + { + // browse any existing filter instances. + var filters = new TsCDaBrowseFilters { ElementNameFilter = null, BrowseFilter = TsCDaBrowseFilter.Item, ReturnAllProperties = false, PropertyIDs = CPX_PROPERTIES, ReturnPropertyValues = true }; + + var elements = server.Browse(_filterItem, filters, out position); + + // nothing found. + if (elements == null || elements.Length == 0) + { + return new TsCCpxComplexItem[0]; + } + + // contruct an array of complex data items for each available data filter. + var dataFilters = new ArrayList(elements.Length); + + Array.ForEach(elements, element => + { + var item = new TsCCpxComplexItem(); + if (item.Init(element)) + dataFilters.Add(item); + }); + + // return the set of available data filters. + return (TsCCpxComplexItem[])dataFilters.ToArray(typeof(TsCCpxComplexItem)); + } + finally + { + if (position != null) + { + position.Dispose(); + position = null; + } + } + } + + /// + /// Creates a new data filter. + /// + /// The server object + /// The name of the filter + /// The value of the filter + public TsCCpxComplexItem CreateDataFilter(TsCDaServer server, string filterName, string filterValue) + { + // not a valid operation for data filter items. + if (_unfilteredItemID != null) + { + return null; + } + + // data filters not supported by the item. + if (_filterItem == null) + { + return null; + } + + TsCDaBrowsePosition position = null; + + try + { + // write the desired filter to the server. + var item = new TsCDaItemValue(_filterItem); + + // create the filter parameters document. + using (var ostrm = new StringWriter()) + { + using (var writer = new XmlTextWriter(ostrm)) + { + writer.WriteStartElement("DataFilters"); + writer.WriteAttributeString("Name", filterName); + writer.WriteString(filterValue); + writer.WriteEndElement(); + writer.Close(); + } + // create the value to write. + item.Value = ostrm.ToString(); + } + item.Quality = TsCDaQuality.Bad; + item.QualitySpecified = false; + item.Timestamp = DateTime.MinValue; + item.TimestampSpecified = false; + + // write the value. + var result = server.Write(new TsCDaItemValue[] { item }); + + if (result == null || result.Length == 0) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "Unexpected results returned from server."); + } + + if (result[0].Result.Failed()) + { + throw new OpcResultException(new OpcResult((int)OpcResult.Cpx.E_FILTER_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), "Could not create new data filter."); + } + + // browse for new data filter item. + var filters = new TsCDaBrowseFilters { ElementNameFilter = filterName, BrowseFilter = TsCDaBrowseFilter.Item, ReturnAllProperties = false, PropertyIDs = CPX_PROPERTIES, ReturnPropertyValues = true }; + + var elements = server.Browse(_filterItem, filters, out position); + + // nothing found. + if (elements == null || elements.Length == 0) + { + throw new OpcResultException(new OpcResult((int)OpcResult.Cpx.E_FILTER_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), "Could not browse to new data filter."); + } + + var filterItem = new TsCCpxComplexItem(); + + if (!filterItem.Init(elements[0])) + { + throw new OpcResultException(new OpcResult((int)OpcResult.Cpx.E_FILTER_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), "Could not initialize to new data filter."); + } + + // return the new data filter. + return filterItem; + } + finally + { + if (position != null) + { + position.Dispose(); + } + } + } + + /// + /// Updates a data filter. + /// + /// The server object + /// The value of the filter + public void UpdateDataFilter(TsCDaServer server, string filterValue) + { + // not a valid operation for non data filter items. + if (_unfilteredItemID == null) + { + throw new OpcResultException(new OpcResult((int)OpcResult.Cpx.E_FILTER_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), "Cannot update the data filter for this item."); + } + + // create the value to write. + var item = new TsCDaItemValue(this) { Value = filterValue, Quality = TsCDaQuality.Bad, QualitySpecified = false, Timestamp = DateTime.MinValue, TimestampSpecified = false }; + + // write the value. + var result = server.Write(new TsCDaItemValue[] { item }); + + if (result == null || result.Length == 0) + { + throw new OpcResultException(new OpcResult((int)OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "Unexpected results returned from server."); + } + + if (result[0].Result.Failed()) + { + throw new OpcResultException(new OpcResult((int)OpcResult.Cpx.E_FILTER_ERROR.Code, OpcResult.FuncCallType.SysFuncCall, null), "Could not update data filter."); + } + + // update locale copy of the filter value. + DataFilterValue = filterValue; + } + + /// + /// Fetches the type dictionary for the item. + /// + /// The server object + public string GetTypeDictionary(TsCDaServer server) + { + var results = server.GetProperties( + new OpcItem[] { _dictionaryItemID }, + new TsDaPropertyID[] { TsDaProperty.DICTIONARY }, + true); + + if (results == null || results.Length == 0 || results[0].Count == 0) + { + return null; + } + + var property = results[0][0]; + + if (!property.Result.Succeeded()) + { + return null; + } + + return (string)property.Value; + } + + /// + /// Fetches the type description for the item. + /// + /// The server object + public string GetTypeDescription(TsCDaServer server) + { + var results = server.GetProperties( + new OpcItem[] { _typeItemID }, + new TsDaPropertyID[] { TsDaProperty.TYPE_DESCRIPTION }, + true); + + if (results == null || results.Length == 0 || results[0].Count == 0) + { + return null; + } + + var property = results[0][0]; + + if (!property.Result.Succeeded()) + { + return null; + } + + return (string)property.Value; + } + + /// + /// Fetches the item id for the data filters items and stores it in the internal cache. + /// + /// The server object + public void GetDataFilterItem(TsCDaServer server) + { + _filterItem = null; + + // not a valid operation for data filter items. + if (_unfilteredItemID != null) + { + return; + } + + TsCDaBrowsePosition position = null; + + try + { + var itemID = new OpcItem(this); + + // browse any existing filter instances. + var filters = new TsCDaBrowseFilters { ElementNameFilter = CPX_DATA_FILTERS, BrowseFilter = TsCDaBrowseFilter.All, ReturnAllProperties = false, PropertyIDs = null, ReturnPropertyValues = false }; + + TsCDaBrowseElement[] elements = null; + + // browse for the 'CPX' branch first. + if (_unconvertedItemID == null) + { + filters.ElementNameFilter = CPX_BRANCH; + + elements = server.Browse(itemID, filters, out position); + + // nothing found. + if (elements == null || elements.Length == 0) + { + return; + } + + // release the position object. + if (position != null) + { + position.Dispose(); + position = null; + } + + // update the item for the next browse operation. + itemID = new OpcItem(elements[0].ItemPath, elements[0].ItemName); + + filters.ElementNameFilter = CPX_DATA_FILTERS; + } + + // browse for the 'DataFilters' branch. + elements = server.Browse(itemID, filters, out position); + + // nothing found. + if (elements == null || elements.Length == 0) + { + return; + } + + _filterItem = new OpcItem(elements[0].ItemPath, elements[0].ItemName); + } + finally + { + if (position != null) + { + position.Dispose(); + } + } + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Private Methods + + /// + /// Sets all object properties to their default values. + /// + private void Clear() + { + _typeSystemID = null; + _dictionaryID = null; + _typeID = null; + _dictionaryItemID = null; + _typeItemID = null; + _unconvertedItemID = null; + _unfilteredItemID = null; + _filterItem = null; + DataFilterValue = null; + } + + /// + /// Initializes the object from a browse element. + /// + private bool Init(TsCDaBrowseElement element) + { + // update the item id. + ItemPath = element.ItemPath; + ItemName = element.ItemName; + Name = element.Name; + + return Init(element.Properties); + } + + /// + /// Initializes the object from a list of properties. + /// + private bool Init(TsCDaItemProperty[] properties) + { + // put the object into default state. + Clear(); + + // must have at least three properties defined. + if (properties == null || properties.Length < 3) + { + return false; + } + + foreach (var property in properties) + { + // continue - ignore invalid properties. + if (!property.Result.Succeeded()) + { + continue; + } + + // type system id. + if (property.ID == TsDaProperty.TYPE_SYSTEM_ID) + { + _typeSystemID = (string)property.Value; + continue; + } + + // dictionary id + if (property.ID == TsDaProperty.DICTIONARY_ID) + { + _dictionaryID = (string)property.Value; + _dictionaryItemID = new OpcItem(property.ItemPath, property.ItemName); + continue; + } + + // type id + if (property.ID == TsDaProperty.TYPE_ID) + { + _typeID = (string)property.Value; + _typeItemID = new OpcItem(property.ItemPath, property.ItemName); + continue; + } + + // unconverted item id + if (property.ID == TsDaProperty.UNCONVERTED_ITEM_ID) + { + _unconvertedItemID = new OpcItem(ItemPath, (string)property.Value); + continue; + } + + // unfiltered item id + if (property.ID == TsDaProperty.UNFILTERED_ITEM_ID) + { + _unfilteredItemID = new OpcItem(ItemPath, (string)property.Value); + continue; + } + + // data filter value. + if (property.ID == TsDaProperty.DATA_FILTER_VALUE) + { + DataFilterValue = (string)property.Value; + continue; + } + } + + // validate object. + if (_typeSystemID == null || _dictionaryID == null || _typeID == null) + { + return false; + } + + return true; + } + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/ComplexTypeCache.cs b/Technosoftware/DaAeHdaClient/Cpx/ComplexTypeCache.cs new file mode 100644 index 0000000..668c874 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/ComplexTypeCache.cs @@ -0,0 +1,207 @@ +#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.Collections; + +using Technosoftware.DaAeHdaClient.Da; +#endregion + +namespace Technosoftware.DaAeHdaClient.Cpx +{ + /// + /// A class that caches properties of complex data items. + /// + public class TsCCpxComplexTypeCache + { + + /////////////////////////////////////////////////////////////////////// + #region Fields + + /// + /// The active server for the application. + /// + private static TsCDaServer _server; + + /// + /// A cache of item properties fetched from the active server. + /// + private static Hashtable _items = new Hashtable(); + + /// + /// A cache of type dictionaries fetched from the active server. + /// + private static Hashtable _dictionaries = new Hashtable(); + + /// + /// A cache of type descriptions fetched from the active server. + /// + private static Hashtable _descriptions = new Hashtable(); + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Constructors, Destructor, Initialization + + /// + /// Initializes the complex type cache with defaults. + /// + public TsCCpxComplexTypeCache() { } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Properties + + /// + /// Get or sets the server to use for the cache. + /// + public static TsCDaServer Server + { + get + { + lock (typeof(TsCCpxComplexTypeCache)) + { + return _server; + } + } + + set + { + lock (typeof(TsCCpxComplexTypeCache)) + { + _server = value; + _items.Clear(); + _dictionaries.Clear(); + _descriptions.Clear(); + } + } + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Public Methods + + /// + /// Returns the complex item for the specified item id. + /// + /// The item id. + public static TsCCpxComplexItem GetComplexItem(OpcItem itemID) + { + if (itemID == null) return null; + + lock (typeof(TsCCpxComplexTypeCache)) + { + var item = new TsCCpxComplexItem(itemID); + + try + { + item.Update(_server); + } + catch + { + // item is not a valid complex data item. + item = null; + } + + _items[itemID.Key] = item; + return item; + } + } + + /// + /// Returns the complex item for the specified item browse element. + /// + /// The item browse element. + public static TsCCpxComplexItem GetComplexItem(TsCDaBrowseElement element) + { + if (element == null) return null; + + lock (typeof(TsCCpxComplexTypeCache)) + { + return GetComplexItem(new OpcItem(element.ItemPath, element.ItemName)); + } + } + + /// + /// Fetches the type dictionary for the item. + /// + /// The item id. + public static string GetTypeDictionary(OpcItem itemID) + { + if (itemID == null) return null; + + lock (typeof(TsCCpxComplexTypeCache)) + { + var dictionary = (string)_dictionaries[itemID.Key]; + + if (dictionary != null) + { + return dictionary; + } + + var item = GetComplexItem(itemID); + + if (item != null) + { + dictionary = item.GetTypeDictionary(_server); + } + + return dictionary; + } + } + + /// + /// Fetches the type description for the item. + /// + /// The item id. + public static string GetTypeDescription(OpcItem itemID) + { + if (itemID == null) return null; + + lock (typeof(TsCCpxComplexTypeCache)) + { + string description = null; + + var item = GetComplexItem(itemID); + + if (item != null) + { + description = (string)_descriptions[item.TypeItemID.Key]; + + if (description != null) + { + return description; + } + + _descriptions[item.TypeItemID.Key] = description = item.GetTypeDescription(_server); + } + + return description; + } + } + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/ComplexValue.cs b/Technosoftware/DaAeHdaClient/Cpx/ComplexValue.cs new file mode 100644 index 0000000..c300386 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/ComplexValue.cs @@ -0,0 +1,49 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Cpx +{ + /// + /// Stores a value with an associated name and/or type. + /// + public class TsCCpxComplexValue + { + /// + /// The name of the value. + /// + public string Name; + + /// + /// The complex or simple data type of the value. + /// + public string Type; + + /// + /// The actual value. + /// + public object Value; + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/Context.cs b/Technosoftware/DaAeHdaClient/Cpx/Context.cs new file mode 100644 index 0000000..1dd2fbb --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/Context.cs @@ -0,0 +1,71 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Cpx +{ + /// + /// Stores the current serialization context. + /// + internal struct TsCCpxContext + { + /////////////////////////////////////////////////////////////////////// + #region Constructors, Destructor, Initialization + + public TsCCpxContext(byte[] buffer) + { + Buffer = buffer; + Index = 0; + Dictionary = null; + Type = null; + BigEndian = false; + CharWidth = 2; + StringEncoding = STRING_ENCODING_UCS2; + FloatFormat = FLOAT_FORMAT_IEEE754; + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Public Fields + + public byte[] Buffer; + public int Index; + public TypeDictionary Dictionary; + public TypeDescription Type; + public bool BigEndian; + public int CharWidth; + public string StringEncoding; + public string FloatFormat; + + + public const string STRING_ENCODING_ACSII = "ASCII"; + public const string STRING_ENCODING_UCS2 = "UCS-2"; + public const string FLOAT_FORMAT_IEEE754 = "IEEE-754"; + + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/InvalidDataInBufferException.cs b/Technosoftware/DaAeHdaClient/Cpx/InvalidDataInBufferException.cs new file mode 100644 index 0000000..faeb042 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/InvalidDataInBufferException.cs @@ -0,0 +1,48 @@ +#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.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Cpx +{ + /// + /// Raised if the data in buffer is not consistent with the schema. + /// + [Serializable] + public class TsCCpxInvalidDataInBufferException : ApplicationException + { + private const string Default = "The data in the buffer cannot be read because it is not consistent with the schema."; + /// + public TsCCpxInvalidDataInBufferException() : base(Default) { } + /// + public TsCCpxInvalidDataInBufferException(string message) : base(Default + Environment.NewLine + message) { } + /// + public TsCCpxInvalidDataInBufferException(Exception e) : base(Default, e) { } + /// + public TsCCpxInvalidDataInBufferException(string message, Exception innerException) : base(Default + Environment.NewLine + message, innerException) { } + /// + protected TsCCpxInvalidDataInBufferException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/InvalidDataToWriteException.cs b/Technosoftware/DaAeHdaClient/Cpx/InvalidDataToWriteException.cs new file mode 100644 index 0000000..8819dcd --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/InvalidDataToWriteException.cs @@ -0,0 +1,48 @@ +#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.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Cpx +{ + /// + /// Raised if the data in buffer is not consistent with the schema. + /// + [Serializable] + public class TsCCpxInvalidDataToWriteException : ApplicationException + { + private const string Default = "The object cannot be written because it is not consistent with the schema."; + /// + public TsCCpxInvalidDataToWriteException() : base(Default) { } + /// + public TsCCpxInvalidDataToWriteException(string message) : base(Default + Environment.NewLine + message) { } + /// + public TsCCpxInvalidDataToWriteException(Exception e) : base(Default, e) { } + /// + public TsCCpxInvalidDataToWriteException(string message, Exception innerException) : base(Default + Environment.NewLine + message, innerException) { } + /// + protected TsCCpxInvalidDataToWriteException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/InvalidSchemaException.cs b/Technosoftware/DaAeHdaClient/Cpx/InvalidSchemaException.cs new file mode 100644 index 0000000..9a8567b --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/InvalidSchemaException.cs @@ -0,0 +1,48 @@ +#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.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Cpx +{ + /// + /// Raised if the schema contains errors or inconsistencies. + /// + [Serializable] + public class TsCCpxInvalidSchemaException : ApplicationException + { + private const string Default = "The schema cannot be used because it contains errors or inconsitencies."; + /// + public TsCCpxInvalidSchemaException() : base(Default) { } + /// + public TsCCpxInvalidSchemaException(string message) : base(Default + Environment.NewLine + message) { } + /// + public TsCCpxInvalidSchemaException(Exception e) : base(Default, e) { } + /// + public TsCCpxInvalidSchemaException(string message, Exception innerException) : base(Default + Environment.NewLine + message, innerException) { } + /// + protected TsCCpxInvalidSchemaException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/OPCBinary.cs b/Technosoftware/DaAeHdaClient/Cpx/OPCBinary.cs new file mode 100644 index 0000000..5bb88be --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/OPCBinary.cs @@ -0,0 +1,269 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 1.1.4322.573 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// +// This source code was auto-generated by xsd, Version=1.1.4322.573. +// +namespace Technosoftware.DaAeHdaClient.Cpx +{ + using System.Xml.Serialization; + + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + [XmlRootAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/", IsNullable=false)] + public class TypeDictionary { + + /// + [XmlElementAttribute("TypeDescription")] + public TypeDescription[] TypeDescription; + + /// + [XmlAttributeAttribute()] + public string DictionaryName; + + /// + [XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(true)] + public bool DefaultBigEndian = true; + + /// + [XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute("UCS-2")] + public string DefaultStringEncoding = "UCS-2"; + + /// + [XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(2)] + public int DefaultCharWidth = 2; + + /// + [XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute("IEEE-754")] + public string DefaultFloatFormat = "IEEE-754"; + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class TypeDescription { + + /// + [XmlElementAttribute("Field")] + public FieldType[] Field; + + /// + [XmlAttributeAttribute()] + public string TypeID; + + /// + [XmlAttributeAttribute()] + public bool DefaultBigEndian; + + /// + [XmlIgnoreAttribute()] + public bool DefaultBigEndianSpecified; + + /// + [XmlAttributeAttribute()] + public string DefaultStringEncoding; + + /// + [XmlAttributeAttribute()] + public int DefaultCharWidth; + + /// + [XmlIgnoreAttribute()] + public bool DefaultCharWidthSpecified; + + /// + [XmlAttributeAttribute()] + public string DefaultFloatFormat; + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + [XmlIncludeAttribute(typeof(TypeReference))] + [XmlIncludeAttribute(typeof(CharString))] + [XmlIncludeAttribute(typeof(Unicode))] + [XmlIncludeAttribute(typeof(Ascii))] + [XmlIncludeAttribute(typeof(FloatingPoint))] + [XmlIncludeAttribute(typeof(Double))] + [XmlIncludeAttribute(typeof(Single))] + [XmlIncludeAttribute(typeof(Integer))] + [XmlIncludeAttribute(typeof(UInt64))] + [XmlIncludeAttribute(typeof(UInt32))] + [XmlIncludeAttribute(typeof(UInt16))] + [XmlIncludeAttribute(typeof(UInt8))] + [XmlIncludeAttribute(typeof(Int64))] + [XmlIncludeAttribute(typeof(Int32))] + [XmlIncludeAttribute(typeof(Int16))] + [XmlIncludeAttribute(typeof(Int8))] + [XmlIncludeAttribute(typeof(BitString))] + public class FieldType { + + /// + [XmlAttributeAttribute()] + public string Name; + + /// + [XmlAttributeAttribute()] + public string Format; + + /// + [XmlAttributeAttribute()] + public int Length; + + /// + [XmlIgnoreAttribute()] + public bool LengthSpecified; + + /// + [XmlAttributeAttribute()] + public int ElementCount; + + /// + [XmlIgnoreAttribute()] + public bool ElementCountSpecified; + + /// + [XmlAttributeAttribute()] + public string ElementCountRef; + + /// + [XmlAttributeAttribute()] + public string FieldTerminator; + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class TypeReference : FieldType { + + /// + [XmlAttributeAttribute()] + public string TypeID; + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + [XmlIncludeAttribute(typeof(Unicode))] + [XmlIncludeAttribute(typeof(Ascii))] + public class CharString : FieldType { + + /// + [XmlAttributeAttribute()] + public int CharWidth; + + /// + [XmlIgnoreAttribute()] + public bool CharWidthSpecified; + + /// + [XmlAttributeAttribute()] + public string StringEncoding; + + /// + [XmlAttributeAttribute()] + public string CharCountRef; + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Unicode : CharString { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Ascii : CharString { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + [XmlIncludeAttribute(typeof(Double))] + [XmlIncludeAttribute(typeof(Single))] + public class FloatingPoint : FieldType { + + /// + [XmlAttributeAttribute()] + public string FloatFormat; + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Double : FloatingPoint { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Single : FloatingPoint { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + [XmlIncludeAttribute(typeof(UInt64))] + [XmlIncludeAttribute(typeof(UInt32))] + [XmlIncludeAttribute(typeof(UInt16))] + [XmlIncludeAttribute(typeof(UInt8))] + [XmlIncludeAttribute(typeof(Int64))] + [XmlIncludeAttribute(typeof(Int32))] + [XmlIncludeAttribute(typeof(Int16))] + [XmlIncludeAttribute(typeof(Int8))] + public class Integer : FieldType { + + /// + [XmlAttributeAttribute()] + [System.ComponentModel.DefaultValueAttribute(true)] + public bool Signed = true; + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class UInt64 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class UInt32 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class UInt16 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class UInt8 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Int64 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Int32 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Int16 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class Int8 : Integer { + } + + /// + [XmlTypeAttribute(Namespace="http://opcfoundation.org/OPCBinary/1.0/")] + public class BitString : FieldType { + } +} diff --git a/Technosoftware/DaAeHdaClient/Cpx/OPCBinary.xsd b/Technosoftware/DaAeHdaClient/Cpx/OPCBinary.xsd new file mode 100644 index 0000000..030f796 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Cpx/OPCBinary.xsd @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Technosoftware/DaAeHdaClient/Da/AccessRight.cs b/Technosoftware/DaAeHdaClient/Da/AccessRight.cs new file mode 100644 index 0000000..f687014 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/AccessRight.cs @@ -0,0 +1,45 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines possible item access rights. + /// Indicates if this item is read only, write only or read/write. + /// This is NOT related to security but rather to the nature of the underlying + /// hardware. + /// + public enum TsDaAccessRights + { + /// The access rights for this item are server. + Unknown = 0x00, + /// The client can read the data item's value. + Readable = 0x01, + /// The client can change the data item's value. + Writable = 0x02, + /// The client can read and change the data item's value. + ReadWritable = 0x03 + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/Browse.cs b/Technosoftware/DaAeHdaClient/Da/Browse.cs new file mode 100644 index 0000000..9f3655f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/Browse.cs @@ -0,0 +1,93 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Stores the state of a browse operation. + /// + [Serializable] + public class TsCDaBrowsePosition : IOpcBrowsePosition + { + #region Fields + private TsCDaBrowseFilters browseFilters_; + private OpcItem itemId_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Saves the parameters for an incomplete browse information. + /// + public TsCDaBrowsePosition(OpcItem itemId, TsCDaBrowseFilters filters) + { + if (filters == null) throw new ArgumentNullException(nameof(filters)); + + itemId_ = (OpcItem)itemId?.Clone(); + browseFilters_ = (TsCDaBrowseFilters)filters.Clone(); + } + + /// + /// Releases unmanaged resources held by the object. + /// + public virtual void Dispose() + { + // does nothing. + } + #endregion + + #region Properties + /// + /// The item identifier of the branch being browsed. + /// + public OpcItem ItemID => itemId_; + + /// + /// The filters applied during the browse operation. + /// + public TsCDaBrowseFilters Filters => (TsCDaBrowseFilters)browseFilters_.Clone(); + + /// + /// The maximum number of elements that may be returned in a single browse. + /// + // ReSharper disable once UnusedMember.Global + public int MaxElementsReturned + { + get => browseFilters_.MaxElementsReturned; + set => browseFilters_.MaxElementsReturned = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return (TsCDaBrowsePosition)MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/BrowseElement.cs b/Technosoftware/DaAeHdaClient/Da/BrowseElement.cs new file mode 100644 index 0000000..d227a0f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/BrowseElement.cs @@ -0,0 +1,87 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Contains a description of an element in the server address space. + /// + [Serializable] + public class TsCDaBrowseElement : ICloneable + { + #region Fields + private TsCDaItemProperty[] itemProperties_ = new TsCDaItemProperty[0]; + #endregion + + #region Properties + /// + /// A descriptive name for element that is unique within a branch. + /// + public string Name { get; set; } + + /// + /// The primary identifier for the element within the server namespace. + /// + public string ItemName { get; set; } + + /// + /// An secondary identifier for the element within the server namespace. + /// + public string ItemPath { get; set; } + + /// + /// Whether the element refers to an item with data that can be accessed. + /// + public bool IsItem { get; set; } + + /// + /// Whether the element has children. + /// + public bool HasChildren { get; set; } + + /// + /// The set of properties for the element. + /// + public TsCDaItemProperty[] Properties + { + get => itemProperties_; + set => itemProperties_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCDaBrowseElement)MemberwiseClone(); + clone.itemProperties_ = (TsCDaItemProperty[])OpcConvert.Clone(itemProperties_); + return clone; + } + #endregion + }; +} diff --git a/Technosoftware/DaAeHdaClient/Da/BrowseFilter.cs b/Technosoftware/DaAeHdaClient/Da/BrowseFilter.cs new file mode 100644 index 0000000..1dfd5fa --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/BrowseFilter.cs @@ -0,0 +1,48 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// The type of browse elements to return during a browse. + /// + public enum TsCDaBrowseFilter + { + /// + /// Return all types of browse elements. + /// + All, + + /// + /// Return only elements that contain other elements. + /// + Branch, + + /// + /// Return only elements that represent items. + /// + Item + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/BrowseFilters.cs b/Technosoftware/DaAeHdaClient/Da/BrowseFilters.cs new file mode 100644 index 0000000..5dec998 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/BrowseFilters.cs @@ -0,0 +1,97 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines a set of filters to apply when browsing. + /// + [Serializable] + public class TsCDaBrowseFilters : ICloneable + { + #region Fields + private TsCDaBrowseFilter browseFilter_ = TsCDaBrowseFilter.All; + private TsDaPropertyID[] propertyIds_; + #endregion + + #region Properties + /// + /// The maximum number of elements to return. Zero means no limit. + /// + public int MaxElementsReturned { get; set; } + + /// + /// The type of element to return. + /// + public TsCDaBrowseFilter BrowseFilter + { + get => browseFilter_; + set => browseFilter_ = value; + } + + /// + /// An expression used to match the name of the element. + /// + public string ElementNameFilter { get; set; } + + /// + /// A filter which has semantics that defined by the server. + /// + public string VendorFilter { get; set; } + + /// + /// Whether all supported properties to return with each element. + /// + public bool ReturnAllProperties { get; set; } + + /// + /// A list of names of the properties to return with each element. + /// + public TsDaPropertyID[] PropertyIDs + { + get => propertyIds_; + set => propertyIds_ = value; + } + + /// + /// Whether property values should be returned with the properties. + /// + public bool ReturnPropertyValues { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCDaBrowseFilters)MemberwiseClone(); + clone.PropertyIDs = (TsDaPropertyID[])PropertyIDs?.Clone(); + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/EuType.cs b/Technosoftware/DaAeHdaClient/Da/EuType.cs new file mode 100644 index 0000000..359060e --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/EuType.cs @@ -0,0 +1,45 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// Defines possible item engineering unit types + public enum TsDaEuType + { + /// No engineering unit information available + NoEnum = 0x01, + /// + /// Analog engineering unit - will contain a SAFE ARRAY of exactly two doubles + /// (VT_ARRAY | VT_R8) corresponding to the LOW and HI EU range. + /// + Analog = 0x02, + /// + /// Enumerated engineering unit - will contain a SAFE ARRAY of strings (VT_ARRAY | + /// VT_BSTR) which contains a list of strings (Example: “OPEN”, “CLOSE”, “IN TRANSIT”, + /// etc.) corresponding to sequential numeric values (0, 1, 2, etc.) + /// + Enumerated = 0x03 + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/IServer.cs b/Technosoftware/DaAeHdaClient/Da/IServer.cs new file mode 100644 index 0000000..41bfb1e --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/IServer.cs @@ -0,0 +1,109 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines functionality that is common to all OPC Data Access servers. + /// + public interface ITsDaServer : IOpcServer + { + /// + /// 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. + int GetResultFilters(); + + /// + /// 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. + void SetResultFilters(int filters); + + /// + /// Returns the current server status. + /// + /// The current server status. + OpcServerStatus GetServerStatus(); + + /// + /// Reads the current values for a set of items. + /// + /// The set of items to read. + /// The results of the read operation for each item. + TsCDaItemValueResult[] Read(TsCDaItem[] items); + + /// + /// Writes the value, quality and timestamp for a set of items. + /// + /// The set of item values to write. + /// The results of the write operation for each item. + OpcItemResult[] Write(TsCDaItemValue[] values); + + /// + /// Creates a new subscription. + /// + /// The initial state of the subscription. + /// The new subscription object. + ITsCDaSubscription CreateSubscription(TsCDaSubscriptionState state); + + /// + /// Cancels a subscription and releases all resources allocated for it. + /// + /// The subscription to cancel. + void CancelSubscription(ITsCDaSubscription subscription); + + /// + /// Fetches the children of a branch that meet the filter criteria. + /// + /// 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. + /// The set of elements found. + TsCDaBrowseElement[] Browse( + OpcItem itemId, + TsCDaBrowseFilters filters, + out TsCDaBrowsePosition position); + + /// + /// Continues a browse operation with previously specified search criteria. + /// + /// An object containing the browse operation state information. + /// The set of elements found. + TsCDaBrowseElement[] BrowseNext(ref TsCDaBrowsePosition 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. + TsCDaItemPropertyCollection[] GetProperties( + OpcItem[] itemIds, + TsDaPropertyID[] propertyIDs, + bool returnValues); + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ISubscription.cs b/Technosoftware/DaAeHdaClient/Da/ISubscription.cs new file mode 100644 index 0000000..8b99595 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ISubscription.cs @@ -0,0 +1,226 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + + /// + /// A subscription for a set of items on a single OPC server. + /// + public interface ITsCDaSubscription : IDisposable + { + #region Events + /// + /// An event to receive data change updates. + /// + event TsCDaDataChangedEventHandler DataChangedEvent; + #endregion + + #region Result Filters + /// + /// 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. + int GetResultFilters(); + + /// + /// 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. + void SetResultFilters(int filters); + #endregion + + #region State Management + /// + /// Returns the current state of the subscription. + /// + /// The current state of the subscription. + TsCDaSubscriptionState GetState(); + + /// + /// Changes the state of a subscription. + /// + /// A bit mask that indicates which elements of the subscription state are changing. + /// The new subscription state. + /// The actual subscription state after applying the changes. + TsCDaSubscriptionState ModifyState(int masks, TsCDaSubscriptionState state); + #endregion + + #region Item Management + /// + /// Adds items to the subscription. + /// + /// The set of items to add to the subscription. + /// The results of the add item operation for each item. + TsCDaItemResult[] AddItems(TsCDaItem[] items); + + /// + /// Modifies the state of items in the subscription + /// + /// Specifies which item state parameters are being modified. + /// The new state for each item. + /// The results of the modify item operation for each item. + TsCDaItemResult[] ModifyItems(int masks, TsCDaItem[] items); + + /// + /// Removes items from the subscription. + /// + /// The identifiers (i.e. server handles) for the items being removed. + /// The results of the remove item operation for each item. + OpcItemResult[] RemoveItems(OpcItem[] items); + #endregion + + #region Synchronous I/O + /// + /// Reads the values for a set of items in the subscription. + /// + /// The identifiers (i.e. server handles) for the items being read. + /// The value for each of items. + TsCDaItemValueResult[] Read(TsCDaItem[] items); + + /// + /// Writes the value, quality and timestamp for a set of items in the subscription. + /// + /// The item values to write. + /// The results of the write operation for each item. + OpcItemResult[] Write(TsCDaItemValue[] items); + #endregion + + #region Asynchronous I/O + /// + /// Begins an asynchronous read operation for a set of items. + /// + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] Read( + TsCDaItem[] items, + object requestHandle, + TsCDaReadCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Begins an asynchronous write operation for a set of items. + /// + /// The set of item values to write (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] Write( + TsCDaItemValue[] items, + object requestHandle, + TsCDaWriteCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Cancels an asynchronous read or write operation. + /// + /// The object returned from the BeginRead or BeginWrite request. + /// The function to invoke when the cancel completes. + void Cancel(IOpcRequest request, TsCDaCancelCompleteEventHandler callback); + + /// + /// Causes the server to send a data changed notification for all active items. + /// + void Refresh(); + + /// + /// Causes the server to send a data changed notification for all active items. + /// + /// An identifier for the request assigned by the caller. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + void Refresh( + object requestHandle, + out IOpcRequest request); + + /// + /// Enables or disables data change notifications from the server. + /// + /// Whether data change notifications are enabled. + void SetEnabled(bool enabled); + + /// + /// Checks whether data change notifications from the server are enabled. + /// + /// Whether data change notifications are enabled. + bool GetEnabled(); + #endregion + } + + #region Delegate Declarations + /// + /// A delegate to receive data change updates from the server. + /// + /// + /// A unique identifier for the subscription assigned by the client. If the parameter + /// ClientHandle is not defined this + /// parameter is empty. + /// + /// + /// An identifier for the request assigned by the caller. This parameter is empty if + /// the corresponding parameter in the calls Read(), Write() or Refresh() is not defined. + /// Can be used to Cancel an outstanding operation. + /// + /// + /// The set of changed values. + /// Each value will always have + /// item’s ClientHandle field specified. + /// + public delegate void TsCDaDataChangedEventHandler(object subscriptionHandle, object requestHandle, TsCDaItemValueResult[] values); + + /// + /// A delegate to receive asynchronous read completed notifications. + /// + /// + /// An identifier for the request assigned by the caller. This parameter is empty if + /// the corresponding parameter in the calls Read(), Write() or Refresh() is not defined. + /// Can be used to Cancel an outstanding operation. + /// + /// The results of the last asynchronous read operation. + public delegate void TsCDaReadCompleteEventHandler(object requestHandle, TsCDaItemValueResult[] results); + + /// + /// A delegate to receive asynchronous write completed notifications. + /// + /// + /// An identifier for the request assigned by the caller. This parameter is empty if + /// the corresponding parameter in the calls Read(), Write() or Refresh() is not defined. + /// Can be used to Cancel an outstanding operation. + /// + /// The results of the last asynchronous write operation. + public delegate void TsCDaWriteCompleteEventHandler(object requestHandle, OpcItemResult[] results); + + /// + /// A delegate to receive asynchronous cancel completed notifications. + /// + /// An identifier for the request assigned by the caller. + public delegate void TsCDaCancelCompleteEventHandler(object requestHandle); + #endregion +} diff --git a/Technosoftware/DaAeHdaClient/Da/Item.cs b/Technosoftware/DaAeHdaClient/Da/Item.cs new file mode 100644 index 0000000..4f720bb --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/Item.cs @@ -0,0 +1,150 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Describes how an item in the server address space should be accessed. + /// + [Serializable] + public class TsCDaItem : OpcItem + { + #region Fields + private bool active_ = true; + private float deadband_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public TsCDaItem() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCDaItem(OpcItem item) + { + if (item == null) + { + return; + } + ItemName = item.ItemName; + ItemPath = item.ItemPath; + ClientHandle = item.ClientHandle; + ServerHandle = item.ServerHandle; + } + + /// + /// Initializes object with the specified Item object. + /// + public TsCDaItem(TsCDaItem item) + : base(item) + { + if (item == null) + { + return; + } + ReqType = item.ReqType; + MaxAge = item.MaxAge; + MaxAgeSpecified = item.MaxAgeSpecified; + Active = item.Active; + ActiveSpecified = item.ActiveSpecified; + Deadband = item.Deadband; + DeadbandSpecified = item.DeadbandSpecified; + SamplingRate = item.SamplingRate; + SamplingRateSpecified = item.SamplingRateSpecified; + EnableBuffering = item.EnableBuffering; + EnableBufferingSpecified = item.EnableBufferingSpecified; + } + #endregion + + #region Properties + /// + /// The data type to use when returning the item value. + /// + public Type ReqType { get; set; } + + /// + /// The oldest (in milliseconds) acceptable cached value when reading an item. + /// + public int MaxAge { get; set; } + + /// + /// Whether the Max Age is specified. + /// + public bool MaxAgeSpecified { get; set; } + + /// + /// Whether the server should send data change updates. + /// + public bool Active + { + get => active_; + set => active_ = value; + } + + /// + /// Whether the Active state is specified. + /// + public bool ActiveSpecified { get; set; } + + /// + /// The minimum percentage change required to trigger a data update for an item. + /// + public float Deadband + { + get => deadband_; + set => deadband_ = value; + } + + /// + /// Whether the Deadband is specified. + /// + public bool DeadbandSpecified { get; set; } + + /// + /// How frequently the server should sample the item value. + /// + public int SamplingRate { get; set; } + + /// + /// Whether the Sampling Rate is specified. + /// + public bool SamplingRateSpecified { get; set; } + + /// + /// Whether the server should buffer multiple data changes between data updates. + /// + public bool EnableBuffering { get; set; } + + /// + /// Whether the Enable Buffering is specified. + /// + public bool EnableBufferingSpecified { get; set; } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ItemCollection.cs b/Technosoftware/DaAeHdaClient/Da/ItemCollection.cs new file mode 100644 index 0000000..0a6caab --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ItemCollection.cs @@ -0,0 +1,313 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// A collection of items. + /// + [Serializable] + public class TsCDaItemCollection : ICloneable, IList + { + #region Fields + private ArrayList items_ = new ArrayList(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCDaItemCollection() { } + + /// + /// Initializes object with the specified ResultCollection object. + /// + public TsCDaItemCollection(TsCDaItemCollection items) + { + if (items == null) + { + return; + } + foreach (TsCDaItem item in items) + { + Add(item); + } + } + #endregion + + #region Properties + /// + /// Gets the item at the specified index. + /// + public TsCDaItem this[int index] + { + get => (TsCDaItem)items_[index]; + set => items_[index] = value; + } + + /// + /// Gets the first item with the specified item id. + /// + public TsCDaItem this[OpcItem itemId] + { + get + { + foreach (TsCDaItem item in items_) + { + if (itemId.Key == item.Key) + { + return item; + } + } + return null; + } + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCDaItemCollection)MemberwiseClone(); + + clone.items_ = new ArrayList(); + + foreach (TsCDaItem item in items_) + { + clone.items_.Add(item.Clone()); + } + + return clone; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => items_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + items_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCDaItem[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return items_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => items_[index]; + + set + { + if (!typeof(TsCDaItem).IsInstanceOfType(value)) + { + throw new ArgumentException("May only add Item objects into the collection."); + } + + items_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + items_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!typeof(TsCDaItem).IsInstanceOfType(value)) + { + throw new ArgumentException("May only add Item objects into the collection."); + } + + items_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + items_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return items_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + items_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return items_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!typeof(TsCDaItem).IsInstanceOfType(value)) + { + throw new ArgumentException("May only add Item objects into the collection."); + } + + return items_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCDaItem value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCDaItem value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCDaItem value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCDaItem value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCDaItem value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ItemProperty.cs b/Technosoftware/DaAeHdaClient/Da/ItemProperty.cs new file mode 100644 index 0000000..dd65433 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ItemProperty.cs @@ -0,0 +1,100 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Contains a description of a single item property. + /// + [Serializable] + public class TsCDaItemProperty : ICloneable, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Properties + + /// + /// The property identifier. + /// + public TsDaPropertyID ID { get; set; } + + /// + /// A short description of the property. + /// + public string Description { get; set; } + + /// + /// The data type of the property. + /// + public Type DataType { get; set; } + + /// + /// The value of the property. + /// + public object Value { get; set; } + + /// + /// The primary identifier for the property if it is directly accessible as an item. + /// + public string ItemName { get; set; } + + /// + /// The secondary identifier for the property if it is directly accessible as an item. + /// + public string ItemPath { get; set; } + #endregion + + #region IOpcResult Members + /// + /// The object with the result of an operation on an property. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCDaItemProperty)MemberwiseClone(); + clone.Value = OpcConvert.Clone(Value); + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ItemPropertyCollection.cs b/Technosoftware/DaAeHdaClient/Da/ItemPropertyCollection.cs new file mode 100644 index 0000000..f5933bd --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ItemPropertyCollection.cs @@ -0,0 +1,175 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// A list of properties for a single item. + /// + [Serializable] + public class TsCDaItemPropertyCollection : ArrayList, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with its default values. + /// + public TsCDaItemPropertyCollection() + { + } + + /// + /// Initializes the object with the specified item identifier. + /// + public TsCDaItemPropertyCollection(OpcItem itemId) + { + if (itemId != null) + { + ItemName = itemId.ItemName; + ItemPath = itemId.ItemPath; + } + } + + /// + /// Initializes the object with the specified item identifier and result. + /// + public TsCDaItemPropertyCollection(OpcItem itemId, OpcResult result) + { + if (itemId != null) + { + ItemName = itemId.ItemName; + ItemPath = itemId.ItemPath; + } + + result_ = result; + } + #endregion + + #region Properties + /// + /// The primary identifier for the item within the server namespace. + /// + public string ItemName { get; set; } + + /// + /// An secondary identifier for the item within the server namespace. + /// + public string ItemPath { get; set; } + + /// + /// Accesses the items at the specified index. + /// + public new TsCDaItemProperty this[int index] + { + get => (TsCDaItemProperty)base[index]; + set => base[index] = value; + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region ICollection Members + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCDaItemProperty[] array, int index) + { + CopyTo((Array)array, index); + } + #endregion + + #region IList Members + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCDaItemProperty value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCDaItemProperty value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCDaItemProperty value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCDaItemProperty value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCDaItemProperty value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ItemResult.cs b/Technosoftware/DaAeHdaClient/Da/ItemResult.cs new file mode 100644 index 0000000..05d6145 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ItemResult.cs @@ -0,0 +1,104 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// The results of an operation on a uniquely identifiable item. + /// + [Serializable] + public class TsCDaItemResult : TsCDaItem, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public TsCDaItemResult() { } + + /// + /// Initializes the object with an ItemIdentifier object. + /// + public TsCDaItemResult(OpcItem item) : base(item) { } + + /// + /// Initializes the object with an ItemIdentifier object and Result. + /// + public TsCDaItemResult(OpcItem item, OpcResult resultId) + : base(item) + { + Result = resultId; + } + + /// + /// Initializes the object with an Item object. + /// + public TsCDaItemResult(TsCDaItem item) : base(item) { } + + /// + /// Initializes the object with an Item object and Result. + /// + public TsCDaItemResult(TsCDaItem item, OpcResult resultId) + : base(item) + { + Result = resultId; + } + + /// + /// Initializes object with the specified ItemResult object. + /// + public TsCDaItemResult(TsCDaItemResult item) + : base(item) + { + if (item != null) + { + Result = item.Result; + DiagnosticInfo = item.DiagnosticInfo; + } + } + #endregion + + #region IOpcResult Members + + /// + /// The error id for the result of an operation on an property. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ItemValue.cs b/Technosoftware/DaAeHdaClient/Da/ItemValue.cs new file mode 100644 index 0000000..c4a403e --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ItemValue.cs @@ -0,0 +1,137 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + + /// + /// Contains the value for a single item. + /// + [Serializable] + public class TsCDaItemValue : OpcItem + { + #region Fields + private TsCDaQuality daQuality_ = TsCDaQuality.Bad; + private DateTime timestamp_ = DateTime.MinValue; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public TsCDaItemValue() { } + + /// + /// Initializes the object with and ItemIdentifier object. + /// + public TsCDaItemValue(OpcItem item) + { + if (item == null) + { + return; + } + ItemName = item.ItemName; + ItemPath = item.ItemPath; + ClientHandle = item.ClientHandle; + ServerHandle = item.ServerHandle; + } + + /// + /// Initializes the object with the specified item name. + /// + public TsCDaItemValue(string itemName) + : base(itemName) + { + } + + /// + /// Initializes object with the specified ItemValue object. + /// + public TsCDaItemValue(TsCDaItemValue item) + : base(item) + { + if (item == null) + { + return; + } + Value = OpcConvert.Clone(item.Value); + Quality = item.Quality; + QualitySpecified = item.QualitySpecified; + Timestamp = item.Timestamp; + TimestampSpecified = item.TimestampSpecified; + } + #endregion + + #region Properties + /// + /// The item value. + /// + public object Value { get; set; } + + /// + /// The quality of the item value. + /// + public TsCDaQuality Quality + { + get => daQuality_; + set => daQuality_ = value; + } + + /// + /// Whether the quality is specified. + /// + public bool QualitySpecified { get; set; } + + /// + /// The timestamp for the item value. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime Timestamp + { + get => timestamp_; + set => timestamp_ = value; + } + + /// + /// Whether the timestamp is specified. + /// + public bool TimestampSpecified { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + var clone = (TsCDaItemValue)MemberwiseClone(); + clone.Value = OpcConvert.Clone(Value); + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ItemValueResult.cs b/Technosoftware/DaAeHdaClient/Da/ItemValueResult.cs new file mode 100644 index 0000000..db97581 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ItemValueResult.cs @@ -0,0 +1,123 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// The results of an operation on a uniquely identifiable item value. + /// + [Serializable] + public class TsCDaItemValueResult : TsCDaItemValue, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public TsCDaItemValueResult() { } + + /// + /// Initializes the object with an ItemIdentifier object. + /// + public TsCDaItemValueResult(OpcItem item) : base(item) { } + + /// + /// Initializes the object with an ItemValue object. + /// + public TsCDaItemValueResult(TsCDaItemValue item) : base(item) { } + + /// + /// Initializes object with the specified ItemValueResult object. + /// + public TsCDaItemValueResult(TsCDaItemValueResult item) + : base(item) + { + if (item != null) + { + Result = item.Result; + DiagnosticInfo = item.DiagnosticInfo; + } + } + + /// + /// Initializes the object with the specified item name and result code. + /// + public TsCDaItemValueResult(string itemName, OpcResult resultId) + : base(itemName) + { + Result = resultId; + } + + /// + /// Initializes the object with the specified item name, result code and diagnostic info. + /// + public TsCDaItemValueResult(string itemName, OpcResult resultId, string diagnosticInfo) + : base(itemName) + { + Result = resultId; + DiagnosticInfo = diagnosticInfo; + } + + /// + /// Initialize object with the specified ItemIdentifier and result code. + /// + public TsCDaItemValueResult(OpcItem item, OpcResult resultId) + : base(item) + { + Result = resultId; + } + + /// + /// Initializes the object with the specified ItemIdentifier, result code and diagnostic info. + /// + public TsCDaItemValueResult(OpcItem item, OpcResult resultId, string diagnosticInfo) + : base(item) + { + Result = resultId; + DiagnosticInfo = diagnosticInfo; + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an property. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/LimitBits.cs b/Technosoftware/DaAeHdaClient/Da/LimitBits.cs new file mode 100644 index 0000000..0e79497 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/LimitBits.cs @@ -0,0 +1,44 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines the possible limit status bits. + /// The Limit Field is valid regardless of the Quality and Substatus. In some + /// cases such as Sensor Failure it can provide useful diagnostic information. + /// + public enum TsDaLimitBits + { + /// The value is free to move up or down + None = 0x0, + /// The value has ‘pegged’ at some lower limit + Low = 0x1, + /// The value has ‘pegged’ at some high limit + High = 0x2, + /// The value is a constant and cannot move + Constant = 0x3 + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/Property.cs b/Technosoftware/DaAeHdaClient/Da/Property.cs new file mode 100644 index 0000000..024c02f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/Property.cs @@ -0,0 +1,328 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines identifiers for well-known properties. + /// + public class TsDaProperty + { + #region Data Access Properties + + /// Item Canonical DataType + public static readonly TsDaPropertyID DATATYPE = new TsDaPropertyID("dataType", 1, OpcNamespace.OPC_DATA_ACCESS); + + /// Item Value + /// + /// Note the type of value returned is as indicated by the "Item Canonical DataType" + /// and depends on the item. This will behave like a read from DEVICE. + /// + public static readonly TsDaPropertyID VALUE = new TsDaPropertyID("value", 2, OpcNamespace.OPC_DATA_ACCESS); + + /// Item Quality + /// (OPCQUALITY stored in an I2). This will behave like a read from DEVICE. + public static readonly TsDaPropertyID QUALITY = new TsDaPropertyID("quality", 3, OpcNamespace.OPC_DATA_ACCESS); + + /// Item Timestamp + /// + /// (will be converted from FILETIME). This will behave like a read from + /// DEVICE. + /// + public static readonly TsDaPropertyID TIMESTAMP = new TsDaPropertyID("timestamp", 4, OpcNamespace.OPC_DATA_ACCESS); + + /// Item Access Rights + /// (OPCACCESSRIGHTS stored in an I4) + public static readonly TsDaPropertyID ACCESSRIGHTS = new TsDaPropertyID("accessRights", 5, OpcNamespace.OPC_DATA_ACCESS); + + /// Server Scan Rate + /// + /// In Milliseconds. This represents the fastest rate at which the server could + /// obtain data from the underlying data source. The nature of this source is not defined + /// but is typically a DCS system, a SCADA system, a PLC via a COMM port or network, a + /// Device Network, etc. This value generally represents the ‘best case’ fastest + /// RequestedUpdateRate which could be used if this item were added to an OPCGroup.
+ /// The accuracy of this value (the ability of the server to attain ‘best case’ + /// performance) can be greatly affected by system load and other factors. + ///
+ public static readonly TsDaPropertyID SCANRATE = new TsDaPropertyID("scanRate", 6, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Indicate the type of Engineering Units (EU) information (if any) contained in + /// EUINFO. + /// + /// + /// 0 - No EU information available (EUINFO will be VT_EMPTY). + /// + /// + /// 1 - Analog - EUINFO will contain a SAFEARRAY of exactly two doubles + /// (VT_ARRAY | VT_R8) corresponding to the LOW and HI EU range. + /// + /// 2 - Enumerated - EUINFO will contain a SAFEARRAY of strings (VT_ARRAY | + /// VT_BSTR) which contains a list of strings (Example: “OPEN”, “CLOSE”, “IN + /// TRANSIT”, etc.) corresponding to sequential numeric values (0, 1, 2, + /// etc.) + /// + /// + /// Item EU Type + public static readonly TsDaPropertyID EUTYPE = new TsDaPropertyID("euType", 7, OpcNamespace.OPC_DATA_ACCESS); + + /// Item EUInfo + /// + /// + /// If EUTYPE is “Analog” EUINFO will contain a SAFEARRAY of exactly two doubles + /// (VT_ARRAY | VT_R8) corresponding to the LOW and HI EU range. + /// + /// If EUTYPE is “Enumerated” - EUINFO will contain a SAFEARRAY of strings + /// (VT_ARRAY | VT_BSTR) which contains a list of strings (Example: “OPEN”, “CLOSE”, + /// “IN TRANSIT”, etc.) corresponding to sequential numeric values (0, 1, 2, + /// etc.) + /// + public static readonly TsDaPropertyID EUINFO = new TsDaPropertyID("euInfo", 8, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// EU Units + /// e.g. "DEGC" or "GALLONS" + /// + public static readonly TsDaPropertyID ENGINEERINGUINTS = new TsDaPropertyID("engineeringUnits", 100, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Item Description + /// e.g. "Evaporator 6 Coolant Temp" + /// + public static readonly TsDaPropertyID DESCRIPTION = new TsDaPropertyID("description", 101, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// High EU + /// Present only for 'analog' data. This represents the highest value likely to + /// be obtained in normal operation and is intended for such use as automatically + /// scaling a bargraph display. + /// e.g. 1400.0 + /// + public static readonly TsDaPropertyID HIGHEU = new TsDaPropertyID("highEU", 102, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Low EU + /// Present only for 'analog' data. This represents the lowest value likely to be + /// obtained in normal operation and is intended for such use as automatically scaling + /// a bargraph display. + /// e.g. -200.0 + /// + public static readonly TsDaPropertyID LOWEU = new TsDaPropertyID("lowEU", 103, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// High Instrument Range + /// Present only for ‘analog’ data. This represents the highest value that can be + /// returned by the instrument. + /// e.g. 9999.9 + /// + public static readonly TsDaPropertyID HIGHIR = new TsDaPropertyID("highIR", 104, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Low Instrument Range + /// Present only for ‘analog’ data. This represents the lowest value that can be + /// returned by the instrument. + /// e.g. -9999.9 + /// + public static readonly TsDaPropertyID LOWIR = new TsDaPropertyID("lowIR", 105, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Contact Close Label + /// Present only for ‘discrete' data. This represents a string to be associated + /// with this contact when it is in the closed (non-zero) state + /// e.g. "RUN", "CLOSE", "ENABLE", "SAFE" ,etc. + /// + public static readonly TsDaPropertyID CLOSELABEL = new TsDaPropertyID("closeLabel", 106, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Contact Open Label + /// Present only for ‘discrete' data. This represents a string to be associated + /// with this contact when it is in the open (zero) state + /// e.g. "STOP", "OPEN", "DISABLE", "UNSAFE" ,etc. + /// + public static readonly TsDaPropertyID OPENLABEL = new TsDaPropertyID("openLabel", 107, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Item Timezone + /// The difference in minutes between the items UTC Timestamp and the local time + /// in which the item value was obtained. + /// + /// + /// See the OPCGroup TimeBias property. Also see the WIN32 TIME_ZONE_INFORMATION + /// structure. + /// + public static readonly TsDaPropertyID TIMEZONE = new TsDaPropertyID("timeZone", 108, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Condition Status + /// The current alarm or condition status associated with the Item
+ /// e.g. "NORMAL", "ACTIVE", "HI ALARM", etc
+ ///
+ public static readonly TsDaPropertyID CONDITION_STATUS = new TsDaPropertyID("conditionStatus", 300, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Alarm Quick Help + /// A short text string providing a brief set of instructions for the operator to + /// follow when this alarm occurs. + /// + public static readonly TsDaPropertyID ALARM_QUICK_HELP = new TsDaPropertyID("alarmQuickHelp", 301, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Alarm Area List + /// An array of stings indicating the plant or alarm areas which include this + /// ItemID. + /// + public static readonly TsDaPropertyID ALARM_AREA_LIST = new TsDaPropertyID("alarmAreaList", 302, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Primary Alarm Area + /// A string indicating the primary plant or alarm area including this + /// ItemID + /// + public static readonly TsDaPropertyID PRIMARY_ALARM_AREA = new TsDaPropertyID("primaryAlarmArea", 303, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Condition Logic + /// An arbitrary string describing the test being performed. + /// e.g. "High Limit Exceeded" or "TAG.PV >= TAG.HILIM" + /// + public static readonly TsDaPropertyID CONDITION_LOGIC = new TsDaPropertyID("conditionLogic", 304, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Limit Exceeded + /// For multistate alarms, the condition exceeded + /// e.g. HIHI, HI, LO, LOLO + /// + public static readonly TsDaPropertyID LIMIT_EXCEEDED = new TsDaPropertyID("limitExceeded", 305, OpcNamespace.OPC_DATA_ACCESS); + + /// Deadband + public static readonly TsDaPropertyID DEADBAND = new TsDaPropertyID("deadband", 306, OpcNamespace.OPC_DATA_ACCESS); + + /// HiHi limit + public static readonly TsDaPropertyID HIHI_LIMIT = new TsDaPropertyID("hihiLimit", 307, OpcNamespace.OPC_DATA_ACCESS); + + /// Hi Limit + public static readonly TsDaPropertyID HI_LIMIT = new TsDaPropertyID("hiLimit", 308, OpcNamespace.OPC_DATA_ACCESS); + + /// Lo Limit + public static readonly TsDaPropertyID LO_LIMIT = new TsDaPropertyID("loLimit", 309, OpcNamespace.OPC_DATA_ACCESS); + + /// LoLo Limit + public static readonly TsDaPropertyID LOLO_LIMIT = new TsDaPropertyID("loloLimit", 310, OpcNamespace.OPC_DATA_ACCESS); + + /// Rate of Change Limit + public static readonly TsDaPropertyID RATE_CHANGE_LIMIT = new TsDaPropertyID("rangeOfChangeLimit", 311, OpcNamespace.OPC_DATA_ACCESS); + + /// Deviation Limit + public static readonly TsDaPropertyID DEVIATION_LIMIT = new TsDaPropertyID("deviationLimit", 312, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Sound File + /// e.g. C:\MEDIA\FIC101.WAV, or .MID + /// + public static readonly TsDaPropertyID SOUNDFILE = new TsDaPropertyID("soundFile", 313, OpcNamespace.OPC_DATA_ACCESS); + #endregion + + #region Complex Data Properties + /// + /// Type System ID + /// Complex Data Property + /// + public static readonly TsDaPropertyID TYPE_SYSTEM_ID = new TsDaPropertyID("typeSystemID", 600, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Dictionary ID + /// Complex Data Property + /// + public static readonly TsDaPropertyID DICTIONARY_ID = new TsDaPropertyID("dictionaryID", 601, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Type ID + /// Complex Data Property + /// + public static readonly TsDaPropertyID TYPE_ID = new TsDaPropertyID("typeID", 602, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Dictionary + /// Complex Data Property + /// + public static readonly TsDaPropertyID DICTIONARY = new TsDaPropertyID("dictionary", 603, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Type description + /// Complex Data Property + /// + public static readonly TsDaPropertyID TYPE_DESCRIPTION = new TsDaPropertyID("typeDescription", 604, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Consistency Window + /// Complex Data Property + /// + public static readonly TsDaPropertyID CONSISTENCY_WINDOW = new TsDaPropertyID("consistencyWindow", 605, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Write Behaviour + /// Complex Data Property, defaults to “All or Nothing” if the complex data item + /// is writable. Not used for Read-Only items. + /// + public static readonly TsDaPropertyID WRITE_BEHAVIOR = new TsDaPropertyID("writeBehavior", 606, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Unconverted Item ID + /// Complex Data Property, the ID of the item that exposes the same complex data + /// value in its native format. This property is mandatory for items that implement + /// complex data type conversions. + /// + public static readonly TsDaPropertyID UNCONVERTED_ITEM_ID = new TsDaPropertyID("unconvertedItemID", 607, OpcNamespace.OPC_DATA_ACCESS); + + + /// + /// Unfiltered Item ID + /// Complex Data Property, the ID the item that exposes the same complex data + /// value without any data filter or with the default query applied to it. It is + /// mandatory for items that implement complex data filters or queries. + /// + public static readonly TsDaPropertyID UNFILTERED_ITEM_ID = new TsDaPropertyID("unfilteredItemID", 608, OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Data Filter Value + /// Complex Data Property, the value of the filter that is currently applied to + /// the item. It is mandatory for items that implement complex data filters or + /// queries. + /// + public static readonly TsDaPropertyID DATA_FILTER_VALUE = new TsDaPropertyID("dataFilterValue", 609, OpcNamespace.OPC_DATA_ACCESS); + #endregion + + #region XML Data Access Properties + /// + public static readonly TsDaPropertyID MINIMUM_VALUE = new TsDaPropertyID("minimumValue", 109, OpcNamespace.OPC_DATA_ACCESS); + /// + public static readonly TsDaPropertyID MAXIMUM_VALUE = new TsDaPropertyID("maximumValue", 110, OpcNamespace.OPC_DATA_ACCESS); + /// + public static readonly TsDaPropertyID VALUE_PRECISION = new TsDaPropertyID("valuePrecision", 111, OpcNamespace.OPC_DATA_ACCESS); + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/PropertyDescription.cs b/Technosoftware/DaAeHdaClient/Da/PropertyDescription.cs new file mode 100644 index 0000000..57701ad --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/PropertyDescription.cs @@ -0,0 +1,200 @@ +#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; +using System.Reflection; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Describes an item property. + /// + [Serializable] + public class TsDaPropertyDescription + { + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with the specified values. + /// + public TsDaPropertyDescription(TsDaPropertyID id, Type type, string name) + { + ID = id; + Type = type; + Name = name; + } + #endregion + + #region Properties + /// + /// The unique identifier for the property. + /// + public TsDaPropertyID ID { get; set; } + + /// + /// The .NET data type for the property. + /// + public Type Type { get; set; } + + /// + /// The short description defined in the OPC specifications. + /// + public string Name { get; set; } + #endregion + + #region Data Access Properties + /// + public static readonly TsDaPropertyDescription DATATYPE = new TsDaPropertyDescription(TsDaProperty.DATATYPE, typeof(Type), "Item Canonical DataType"); + /// + public static readonly TsDaPropertyDescription VALUE = new TsDaPropertyDescription(TsDaProperty.VALUE, typeof(object), "Item Value"); + /// + public static readonly TsDaPropertyDescription QUALITY = new TsDaPropertyDescription(TsDaProperty.QUALITY, typeof(TsCDaQuality), "Item Quality"); + /// + public static readonly TsDaPropertyDescription TIMESTAMP = new TsDaPropertyDescription(TsDaProperty.TIMESTAMP, typeof(DateTime), "Item Timestamp"); + /// + public static readonly TsDaPropertyDescription ACCESSRIGHTS = new TsDaPropertyDescription(TsDaProperty.ACCESSRIGHTS, typeof(TsDaAccessRights), "Item Access Rights"); + /// + public static readonly TsDaPropertyDescription SCANRATE = new TsDaPropertyDescription(TsDaProperty.SCANRATE, typeof(float), "Server Scan Rate"); + /// + public static readonly TsDaPropertyDescription EUTYPE = new TsDaPropertyDescription(TsDaProperty.EUTYPE, typeof(TsDaEuType), "Item EU Type"); + /// + public static readonly TsDaPropertyDescription EUINFO = new TsDaPropertyDescription(TsDaProperty.EUINFO, typeof(string[]), "Item EU Info"); + /// + public static readonly TsDaPropertyDescription ENGINEERINGUINTS = new TsDaPropertyDescription(TsDaProperty.ENGINEERINGUINTS, typeof(string), "EU Units"); + /// + public static readonly TsDaPropertyDescription DESCRIPTION = new TsDaPropertyDescription(TsDaProperty.DESCRIPTION, typeof(string), "Item Description"); + /// + public static readonly TsDaPropertyDescription HIGHEU = new TsDaPropertyDescription(TsDaProperty.HIGHEU, typeof(double), "High EU"); + /// + public static readonly TsDaPropertyDescription LOWEU = new TsDaPropertyDescription(TsDaProperty.LOWEU, typeof(double), "Low EU"); + /// + public static readonly TsDaPropertyDescription HIGHIR = new TsDaPropertyDescription(TsDaProperty.HIGHIR, typeof(double), "High Instrument Range"); + /// + public static readonly TsDaPropertyDescription LOWIR = new TsDaPropertyDescription(TsDaProperty.LOWIR, typeof(double), "Low Instrument Range"); + /// + public static readonly TsDaPropertyDescription CLOSELABEL = new TsDaPropertyDescription(TsDaProperty.CLOSELABEL, typeof(string), "Contact Close Label"); + /// + public static readonly TsDaPropertyDescription OPENLABEL = new TsDaPropertyDescription(TsDaProperty.OPENLABEL, typeof(string), "Contact Open Label"); + /// + public static readonly TsDaPropertyDescription TIMEZONE = new TsDaPropertyDescription(TsDaProperty.TIMEZONE, typeof(int), "Timezone"); + /// + public static readonly TsDaPropertyDescription CONDITION_STATUS = new TsDaPropertyDescription(TsDaProperty.CONDITION_STATUS, typeof(string), "Condition Status"); + /// + public static readonly TsDaPropertyDescription ALARM_QUICK_HELP = new TsDaPropertyDescription(TsDaProperty.ALARM_QUICK_HELP, typeof(string), "Alarm Quick Help"); + /// + public static readonly TsDaPropertyDescription ALARM_AREA_LIST = new TsDaPropertyDescription(TsDaProperty.ALARM_AREA_LIST, typeof(string), "Alarm Area List"); + /// + public static readonly TsDaPropertyDescription PRIMARY_ALARM_AREA = new TsDaPropertyDescription(TsDaProperty.PRIMARY_ALARM_AREA, typeof(string), "Primary Alarm Area"); + /// + public static readonly TsDaPropertyDescription CONDITION_LOGIC = new TsDaPropertyDescription(TsDaProperty.CONDITION_LOGIC, typeof(string), "Condition Logic"); + /// + public static readonly TsDaPropertyDescription LIMIT_EXCEEDED = new TsDaPropertyDescription(TsDaProperty.LIMIT_EXCEEDED, typeof(string), "Limit Exceeded"); + /// + public static readonly TsDaPropertyDescription DEADBAND = new TsDaPropertyDescription(TsDaProperty.DEADBAND, typeof(double), "Deadband"); + /// + public static readonly TsDaPropertyDescription HIHI_LIMIT = new TsDaPropertyDescription(TsDaProperty.HIHI_LIMIT, typeof(double), "HiHi Limit"); + /// + public static readonly TsDaPropertyDescription HI_LIMIT = new TsDaPropertyDescription(TsDaProperty.HI_LIMIT, typeof(double), "Hi Limit"); + /// + public static readonly TsDaPropertyDescription LO_LIMIT = new TsDaPropertyDescription(TsDaProperty.LO_LIMIT, typeof(double), "Lo Limit"); + /// + public static readonly TsDaPropertyDescription LOLO_LIMIT = new TsDaPropertyDescription(TsDaProperty.LOLO_LIMIT, typeof(double), "LoLo Limit"); + /// + public static readonly TsDaPropertyDescription RATE_CHANGE_LIMIT = new TsDaPropertyDescription(TsDaProperty.RATE_CHANGE_LIMIT, typeof(double), "Rate of Change Limit"); + /// + public static readonly TsDaPropertyDescription DEVIATION_LIMIT = new TsDaPropertyDescription(TsDaProperty.DEVIATION_LIMIT, typeof(double), "Deviation Limit"); + /// + public static readonly TsDaPropertyDescription SOUNDFILE = new TsDaPropertyDescription(TsDaProperty.SOUNDFILE, typeof(string), "Sound File"); + #endregion + + #region Complex Data Properties + /// + public static readonly TsDaPropertyDescription TYPE_SYSTEM_ID = new TsDaPropertyDescription(TsDaProperty.TYPE_SYSTEM_ID, typeof(string), "Type System ID"); + /// + public static readonly TsDaPropertyDescription DICTIONARY_ID = new TsDaPropertyDescription(TsDaProperty.DICTIONARY_ID, typeof(string), "Dictionary ID"); + /// + public static readonly TsDaPropertyDescription TYPE_ID = new TsDaPropertyDescription(TsDaProperty.TYPE_ID, typeof(string), "Type ID"); + /// + public static readonly TsDaPropertyDescription DICTIONARY = new TsDaPropertyDescription(TsDaProperty.DICTIONARY, typeof(object), "Dictionary"); + /// + public static readonly TsDaPropertyDescription TYPE_DESCRIPTION = new TsDaPropertyDescription(TsDaProperty.TYPE_DESCRIPTION, typeof(string), "Type Description"); + /// + public static readonly TsDaPropertyDescription CONSISTENCY_WINDOW = new TsDaPropertyDescription(TsDaProperty.CONSISTENCY_WINDOW, typeof(string), "Consistency Window"); + /// + public static readonly TsDaPropertyDescription WRITE_BEHAVIOR = new TsDaPropertyDescription(TsDaProperty.WRITE_BEHAVIOR, typeof(string), "Write Behavior"); + /// + public static readonly TsDaPropertyDescription UNCONVERTED_ITEM_ID = new TsDaPropertyDescription(TsDaProperty.UNCONVERTED_ITEM_ID, typeof(string), "Unconverted Item ID"); + /// + public static readonly TsDaPropertyDescription UNFILTERED_ITEM_ID = new TsDaPropertyDescription(TsDaProperty.UNFILTERED_ITEM_ID, typeof(string), "Unfiltered Item ID"); + /// + public static readonly TsDaPropertyDescription DATA_FILTER_VALUE = new TsDaPropertyDescription(TsDaProperty.DATA_FILTER_VALUE, typeof(string), "Data Filter Value"); + #endregion + + #region Object Member Overrides + /// + /// Converts the description to a string. + /// + public override string ToString() + { + return Name; + } + #endregion + + #region Public Methods + /// + /// Returns the description for the specified property. + /// + public static TsDaPropertyDescription Find(TsDaPropertyID id) + { + var fields = typeof(TsDaPropertyDescription).GetFields(BindingFlags.Static | BindingFlags.Public); + + foreach (var field in fields) + { + var property = (TsDaPropertyDescription)field.GetValue(typeof(TsDaPropertyDescription)); + + if (property.ID == id) + { + return property; + } + } + + return null; + } + + /// + /// Returns an array of all well-known property descriptions. + /// + public static TsDaPropertyDescription[] Enumerate() + { + var values = new ArrayList(); + + var fields = typeof(TsDaPropertyDescription).GetFields(BindingFlags.Static | BindingFlags.Public); + + Array.ForEach(fields, field => values.Add(field.GetValue(typeof(TsDaPropertyDescription)))); + + return (TsDaPropertyDescription[])values.ToArray(typeof(TsDaPropertyDescription)); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/PropertyID.cs b/Technosoftware/DaAeHdaClient/Da/PropertyID.cs new file mode 100644 index 0000000..6027bb0 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/PropertyID.cs @@ -0,0 +1,198 @@ +#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.Xml; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Contains a unique identifier for a property. + /// + [Serializable] + public struct TsDaPropertyID : ISerializable + { + #region Names Class + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Name = "NA"; + internal const string Namespace = "NS"; + internal const string Code = "CO"; + } + #endregion + + #region Fields + private int code_; + private XmlQualifiedName qualifiedName_; + #endregion + + #region Constructors, Destructor, Initialization + + /// + /// Initializes a property identified by a qualified name. + /// + public TsDaPropertyID(XmlQualifiedName name) { qualifiedName_ = name; code_ = 0; } + + /// + /// Initializes a property identified by an integer. + /// + public TsDaPropertyID(int code) { qualifiedName_ = null; code_ = code; } + + /// + /// Initializes a property identified by a property description. + /// + public TsDaPropertyID(string name, int code, string ns) { qualifiedName_ = new XmlQualifiedName(name, ns); code_ = code; } + + /// + /// During deserialization, SerializationInfo is passed to the class using the constructor provided for this purpose. Any visibility + /// constraints placed on the constructor are ignored when the object is deserialized; so you can mark the class as public, + /// protected, internal, or private. However, it is best practice to make the constructor protected unless the class is sealed, in which case + /// the constructor should be marked private. The constructor should also perform thorough input validation. To avoid misuse by malicious code, + /// the constructor should enforce the same security checks and permissions required to obtain an instance of the class using any other + /// constructor. + /// + /// + /// Constructs a server by de-serializing its OpcUrl from the stream. + /// + private TsDaPropertyID(SerializationInfo info, StreamingContext context) + { + var enumerator = info.GetEnumerator(); + var name = ""; + var ns = ""; + enumerator.Reset(); + while (enumerator.MoveNext()) + { + if (enumerator.Current.Name.Equals(Names.Name)) + { + name = (string)enumerator.Current.Value; + continue; + } + if (enumerator.Current.Name.Equals(Names.Namespace)) + { + ns = (string)enumerator.Current.Value; + } + } + qualifiedName_ = new XmlQualifiedName(name, ns); + code_ = (int)info.GetValue(Names.Code, typeof(int)); + } + #endregion + + #region Properties + /// + /// Used for properties identified by a qualified name. + /// + public XmlQualifiedName Name => qualifiedName_; + + /// + /// Used for properties identified by a integer. + /// + public int Code => code_; + #endregion + + #region Public Methods + /// + /// Returns true if the objects are equal. + /// + public static bool operator ==(TsDaPropertyID a, TsDaPropertyID b) + { + return a.Equals(b); + } + + /// + /// Returns true if the objects are not equal. + /// + public static bool operator !=(TsDaPropertyID a, TsDaPropertyID b) + { + return !a.Equals(b); + } + #endregion + + #region Serialization Functions + /// + /// Serializes a server into a stream. + /// + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (qualifiedName_ != null) + { + info.AddValue(Names.Name, qualifiedName_.Name); + info.AddValue(Names.Namespace, qualifiedName_.Namespace); + } + info.AddValue(Names.Code, code_); + } + #endregion + + #region Object Member Overrides + /// + /// Returns true if the target object is equal to the object. + /// + public override bool Equals(object target) + { + if (target != null && target.GetType() == typeof(TsDaPropertyID)) + { + var propertyId = (TsDaPropertyID)target; + + // compare by integer if both specify valid integers. + if (propertyId.Code != 0 && Code != 0) + { + return (propertyId.Code == Code); + } + + // compare by name if both specify valid names. + if (propertyId.Name != null && Name != null) + { + return (propertyId.Name == Name); + } + } + + return false; + } + + /// + /// Returns a useful hash code for the object. + /// + public override int GetHashCode() + { + if (Code != 0) return Code.GetHashCode(); + if (Name != null) return Name.GetHashCode(); + return base.GetHashCode(); + } + + /// + /// Converts the property id to a string. + /// + public override string ToString() + { + if (Name != null && Code != 0) return $"{Name.Name} ({Code})"; + if (Name != null) return Name.Name; + if (Code != 0) return $"{Code}"; + return ""; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/Quality.cs b/Technosoftware/DaAeHdaClient/Da/Quality.cs new file mode 100644 index 0000000..3bbce47 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/Quality.cs @@ -0,0 +1,251 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + + /// + /// Contains the quality field for an item value. + /// + [Serializable] + public struct TsCDaQuality + { + #region Fields + private TsDaQualityBits qualityBits_; + private TsDaLimitBits limitBits_; + private byte vendorBits_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with the specified quality. + /// + public TsCDaQuality(TsDaQualityBits quality) + { + qualityBits_ = quality; + limitBits_ = TsDaLimitBits.None; + vendorBits_ = 0; + } + + /// + /// Initializes the object from the contents of a 16 bit integer. + /// + public TsCDaQuality(short code) + { + qualityBits_ = (TsDaQualityBits)(code & (short)TsDaQualityMasks.QualityMask); + limitBits_ = (TsDaLimitBits)(code & (short)TsDaQualityMasks.LimitMask); + vendorBits_ = (byte)((code & (short)TsDaQualityMasks.VendorMask) >> 8); + } + #endregion + + #region Properties + /// + /// The value in the quality bits field. + /// + public TsDaQualityBits QualityBits + { + get => qualityBits_; + set => qualityBits_ = value; + } + + /// + /// The value in the limit bits field. + /// + public TsDaLimitBits LimitBits + { + get => limitBits_; + set => limitBits_ = value; + } + + /// + /// The value in the quality bits field. + /// + public byte VendorBits + { + get => vendorBits_; + set => vendorBits_ = value; + } + + /// + /// A 'good' quality value. + /// + public static readonly TsCDaQuality Good = new TsCDaQuality(TsDaQualityBits.Good); + + /// + /// An 'bad' quality value. + /// + public static readonly TsCDaQuality Bad = new TsCDaQuality(TsDaQualityBits.Bad); + #endregion + + #region Public Methods + /// + /// Returns the quality as a 16 bit integer. + /// + public short GetCode() + { + ushort code = 0; + + code |= (ushort)QualityBits; + code |= (ushort)LimitBits; + code |= (ushort)(VendorBits << 8); + + return (code <= short.MaxValue) ? (short)code : (short)-((ushort.MaxValue + 1 - code)); + } + + /// + /// Initializes the quality from a 16 bit integer. + /// + public void SetCode(short code) + { + qualityBits_ = (TsDaQualityBits)(code & (short)TsDaQualityMasks.QualityMask); + limitBits_ = (TsDaLimitBits)(code & (short)TsDaQualityMasks.LimitMask); + vendorBits_ = (byte)((code & (short)TsDaQualityMasks.VendorMask) >> 8); + } + + /// + /// Returns true if the objects are equal. + /// + public static bool operator ==(TsCDaQuality a, TsCDaQuality b) + { + return a.Equals(b); + } + + /// + /// Returns true if the objects are not equal. + /// + public static bool operator !=(TsCDaQuality a, TsCDaQuality b) + { + return !a.Equals(b); + } + #endregion + + #region Object Member Overrides + /// + /// Converts a quality to a string with the format: 'quality[limit]:vendor'. + /// + public override string ToString() + { + string text = null; + + switch (QualityBits) + { + case TsDaQualityBits.Good: + text += "(Good"; + break; + case TsDaQualityBits.GoodLocalOverride: + text += "(Good:Local Override"; + break; + case TsDaQualityBits.Bad: + text += "(Bad"; + break; + case TsDaQualityBits.BadConfigurationError: + text += "(Bad:Configuration Error"; + break; + case TsDaQualityBits.BadNotConnected: + text += "(Bad:Not Connected"; + break; + case TsDaQualityBits.BadDeviceFailure: + text += "(Bad:Device Failure"; + break; + case TsDaQualityBits.BadSensorFailure: + text += "(Bad:Sensor Failure"; + break; + case TsDaQualityBits.BadLastKnownValue: + text += "(Bad:Last Known Value"; + break; + case TsDaQualityBits.BadCommFailure: + text += "(Bad:Communication Failure"; + break; + case TsDaQualityBits.BadOutOfService: + text += "(Bad:Out of Service"; + break; + case TsDaQualityBits.BadWaitingForInitialData: + text += "(Bad:Waiting for Initial Data"; + break; + case TsDaQualityBits.Uncertain: + text += "(Uncertain"; + break; + case TsDaQualityBits.UncertainLastUsableValue: + text += "(Uncertain:Last Usable Value"; + break; + case TsDaQualityBits.UncertainSensorNotAccurate: + text += "(Uncertain:Sensor not Accurate"; + break; + case TsDaQualityBits.UncertainEUExceeded: + text += "(Uncertain:Engineering Unit exceeded"; + break; + case TsDaQualityBits.UncertainSubNormal: + text += "(Uncertain:Sub Normal"; + break; + } + + if (LimitBits != TsDaLimitBits.None) + { + text += $":[{LimitBits.ToString()}]"; + } + else + { + text += ":Not Limited"; + } + + if (VendorBits != 0) + { + text += $":{VendorBits,0:X})"; + } + else + { + text += ")"; + } + + return text; + } + + /// + /// Determines whether the specified Object is equal to the current Quality + /// + public override bool Equals(object target) + { + if (target == null || target.GetType() != typeof(TsCDaQuality)) return false; + + var quality = (TsCDaQuality)target; + + if (QualityBits != quality.QualityBits) return false; + if (LimitBits != quality.LimitBits) return false; + if (VendorBits != quality.VendorBits) return false; + + return true; + } + + /// + /// Returns hash code for the current Quality. + /// + public override int GetHashCode() + { + return GetCode(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/QualityBits.cs b/Technosoftware/DaAeHdaClient/Da/QualityBits.cs new file mode 100644 index 0000000..337b719 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/QualityBits.cs @@ -0,0 +1,114 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines the possible quality status bits. + /// These flags represent the quality state for an item's data value. This is + /// intended to be similar to but slightly simpler than the Field-bus Data Quality + /// Specification (section 4.4.1 in the H1 Final Specifications). This design makes it + /// fairly easy for both servers and client applications to determine how much + /// functionality they want to implement. + /// + public enum TsDaQualityBits + { + /// The Quality of the value is Good. + Good = 0x000000C0, + /// The value has been Overridden. Typically this means the input has been disconnected and a manually entered value has been 'forced'. + GoodLocalOverride = 0x000000D8, + /// The value is bad but no specific reason is known. + Bad = 0x00000000, + /// + /// There is some server specific problem with the configuration. For example the + /// item in question has been deleted from the configuration. + /// + BadConfigurationError = 0x00000004, + /// + /// The input is required to be logically connected to something but is not. This + /// quality may reflect that no value is available at this time, for reasons like the value + /// may have not been provided by the data source. + /// + BadNotConnected = 0x00000008, + /// A device failure has been detected. + BadDeviceFailure = 0x0000000c, + /// + /// A sensor failure had been detected (the Limits field can provide additional + /// diagnostic information in some situations). + /// + BadSensorFailure = 0x00000010, + /// + /// Communications have failed. However, the last known value is available. Note that + /// the age of the value may be determined from the time stamp in the item state. + /// + BadLastKnownValue = 0x00000014, + /// Communications have failed. There is no last known value is available. + BadCommFailure = 0x00000018, + /// + /// The block is off scan or otherwise locked. This quality is also used when the + /// active state of the item or the subscription containing the item is InActive. + /// + BadOutOfService = 0x0000001C, + /// + /// After Items are added to a subscription, it may take some time for the server to + /// actually obtain values for these items. In such cases the client might perform a read + /// (from cache), or establish a ConnectionPoint based subscription and/or execute a + /// Refresh on such a subscription before the values are available. This sub-status is only + /// available from OPC DA 3.0 or newer servers. + /// + BadWaitingForInitialData = 0x00000020, + /// There is no specific reason why the value is uncertain. + Uncertain = 0x00000040, + /// + /// Whatever was writing this value has stopped doing so. The returned value should + /// be regarded as stale. Note that this differs from a BAD value with sub-status + /// badLastKnownValue (Last Known Value). That status is associated specifically with a + /// detectable communications error on a fetched value. This error is associated with the + /// failure of some external source to put something into the value within an acceptable + /// period of time. Note that the age of the value can be determined from the time stamp + /// in the item state. + /// + UncertainLastUsableValue = 0x00000044, + /// + /// Either the value has pegged at one of the sensor limits (in which case the + /// limit field should be set to low or high) or the sensor is otherwise known to be out of + /// calibration via some form of internal diagnostics (in which case the limit field should + /// be none). + /// + UncertainSensorNotAccurate = 0x00000050, + /// + /// The returned value is outside the limits defined for this parameter. Note that in + /// this case (per the Field-bus Specification) the Limits field indicates which limit has + /// been exceeded but does NOT necessarily imply that the value cannot move farther out of + /// range. + /// + UncertainEUExceeded = 0x00000054, + /// + /// The value is derived from multiple sources and has less than the required number + /// of Good sources. + /// + UncertainSubNormal = 0x00000058 + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/QualityMasks.cs b/Technosoftware/DaAeHdaClient/Da/QualityMasks.cs new file mode 100644 index 0000000..449fc3d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/QualityMasks.cs @@ -0,0 +1,40 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines bit masks for the quality. + /// + public enum TsDaQualityMasks : int + { + /// Quality related bits + QualityMask = +0x00FC, + /// Limit related bits + LimitMask = +0x0003, + /// Vendor specific bits + VendorMask = -0x00FD + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/Request.cs b/Technosoftware/DaAeHdaClient/Da/Request.cs new file mode 100644 index 0000000..9045de5 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/Request.cs @@ -0,0 +1,70 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Describes the state of a subscription. + /// + [Serializable] + public class TsCDaRequest : IOpcRequest + { + #region Fields + private ITsCDaSubscription subscription_; + private object handle_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with a subscription and a unique id. + /// + public TsCDaRequest(ITsCDaSubscription subscription, object handle) + { + subscription_ = subscription; + handle_ = handle; + } + #endregion + + #region Properties + /// + /// The subscription processing the request. + /// + public ITsCDaSubscription Subscription => subscription_; + + /// + /// An unique identifier, assigned by the client, for the request. + /// + public object Handle => handle_; + #endregion + + #region Public Methods + /// + /// Cancels the request, if possible. + /// + public void Cancel(TsCDaCancelCompleteEventHandler callback) { subscription_.Cancel(this, callback); } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ResultFilter.cs b/Technosoftware/DaAeHdaClient/Da/ResultFilter.cs new file mode 100644 index 0000000..c9c3889 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ResultFilter.cs @@ -0,0 +1,75 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Filters applied by the server before returning item results. + /// + [Flags] + public enum TsCDaResultFilter + { + /// + /// Include the ItemName in the ItemIdentifier if bit is set. + /// + ItemName = 0x01, + + /// + /// Include the ItemPath in the ItemIdentifier if bit is set. + /// + ItemPath = 0x02, + + /// + /// Include the ClientHandle in the ItemIdentifier if bit is set. + /// + ClientHandle = 0x04, + + /// + /// Include the Timestamp in the ItemValue if bit is set. + /// + ItemTime = 0x08, + + /// + /// Include verbose, localized error text with result if bit is set. + /// + ErrorText = 0x10, + + /// + /// Include additional diagnostic information with result if bit is set. + /// + DiagnosticInfo = 0x20, + + /// + /// Include the ItemName and Timestamp if bit is set. + /// + Minimal = ItemName | ItemTime, + + /// + /// Include all information in the results if bit is set. + /// + All = 0x3F + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/Server.cs b/Technosoftware/DaAeHdaClient/Da/Server.cs new file mode 100644 index 0000000..11f6e31 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/Server.cs @@ -0,0 +1,500 @@ +#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.Runtime.Serialization; +using System.Collections.Generic; +#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 + + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/ServerState.cs b/Technosoftware/DaAeHdaClient/Da/ServerState.cs new file mode 100644 index 0000000..bf4d6c3 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/ServerState.cs @@ -0,0 +1,68 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// The set of possible server states. + /// + public enum TsCDaServerState + { + /// + /// The server state is not known. + /// + Unknown, + + /// + /// The server is running normally. + /// + Running, + + /// + /// The server is not functioning due to a fatal error. + /// + Failed, + + /// + /// The server cannot load its configuration information. + /// + NoConfig, + + /// + /// The server has halted all communication with the underlying hardware. + /// + Suspended, + + /// + /// The server is disconnected from the underlying hardware. + /// + Test, + + /// + /// The server cannot communicate with the underlying hardware. + /// + CommFault + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/StateMask.cs b/Technosoftware/DaAeHdaClient/Da/StateMask.cs new file mode 100644 index 0000000..03266c3 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/StateMask.cs @@ -0,0 +1,92 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Defines masks to be used when modifying the subscription or item state. + /// + [Flags] + public enum TsCDaStateMask + { + /// + /// The name of the subscription. + /// + Name = 0x0001, + + /// + /// The client assigned handle for the item or subscription. + /// + ClientHandle = 0x0002, + + /// + /// The locale to use for results returned to the client from the subscription. + /// + Locale = 0x0004, + + /// + /// Whether the item or subscription is active. + /// + Active = 0x0008, + + /// + /// The maximum rate at which data update notifications are sent. + /// + UpdateRate = 0x0010, + + /// + /// The longest period between data update notifications.
+ /// Note: This feature is only supported with OPC Data Access 3.0 + /// Servers. + ///
+ KeepAlive = 0x0020, + + /// + /// The requested data type for the item. + /// + ReqType = 0x0040, + + /// + /// The deadband for the item or subscription. + /// + Deadband = 0x0080, + + /// + /// The rate at which the server should check for changes to an item value. + /// + SamplingRate = 0x0100, + + /// + /// Whether the server should buffer multiple changes to a single item. + /// + EnableBuffering = 0x0200, + + /// + /// All fields are valid. + /// + All = 0xFFFF + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/Subscription.cs b/Technosoftware/DaAeHdaClient/Da/Subscription.cs new file mode 100644 index 0000000..1b3d1ba --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/Subscription.cs @@ -0,0 +1,571 @@ +#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; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// An in-process object used to access subscriptions on OPC Data Access servers. + /// + [Serializable] + public class TsCDaSubscription : ITsCDaSubscription, ISerializable, ICloneable + { + #region Names Class + + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string State = "State"; + internal const string Filters = "Filters"; + internal const string Items = "Items"; + } + + #endregion + + #region Fields + + private bool disposed_; + + /// + /// The containing server object. + /// + private TsCDaServer server_; + + /// + /// The remote subscription object. + /// + internal ITsCDaSubscription Subscription; + + /// + /// The local copy of the subscription state. + /// + private TsCDaSubscriptionState subscriptionState_ = new TsCDaSubscriptionState(); + + /// + /// The local copy of all subscription items. + /// + private TsCDaItem[] daItems_; + + /// + /// Whether data callbacks are enabled. + /// + private bool enabled_ = true; + + /// + /// The local copy of the result filters. + /// + private int filters_ = (int)TsCDaResultFilter.All | (int)TsCDaResultFilter.ClientHandle; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with default values. + /// + public TsCDaSubscription(TsCDaServer server, ITsCDaSubscription subscription) + { + server_ = server ?? throw new ArgumentNullException(nameof(server)); + Subscription = subscription ?? throw new ArgumentNullException(nameof(subscription)); + + GetResultFilters(); + GetState(); + } + + /// + /// Constructs a server by de-serializing its OpcUrl from the stream. + /// + protected TsCDaSubscription(SerializationInfo info, StreamingContext context) + { + subscriptionState_ = (TsCDaSubscriptionState)info.GetValue(Names.State, typeof(TsCDaSubscriptionState)); + filters_ = (int)info.GetValue(Names.Filters, typeof(int)); + daItems_ = (TsCDaItem[])info.GetValue(Names.Items, typeof(TsCDaItem[])); + } + + /// + /// The finalizer implementation. + /// + ~TsCDaSubscription() + { + Dispose(false); + } + + /// + /// This must be called explicitly by clients to ensure the remote server is released. + /// + public virtual void Dispose() + { + Dispose(true); + // Take yourself off the Finalization queue + // to prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// If true managed and unmanaged resources can be disposed. If false only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!disposed_) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + if (Subscription != null) + { + Subscription.Dispose(); + + server_ = null; + Subscription = null; + daItems_ = null; + } + } + // Release unmanaged resources. If disposing is false, + // only the following code is executed. + } + disposed_ = true; + } + #endregion + + #region Properties + /// + /// The server that the subscription is attached to. + /// + public TsCDaServer Server => server_; + + /// + /// The name assigned to the subscription by the client. + /// + public string Name => subscriptionState_.Name; + + /// + /// The handle assigned to the subscription by the client. + /// + public object ClientHandle => subscriptionState_.ClientHandle; + + /// + /// The handle assigned to the subscription by the server. + /// + public object ServerHandle => subscriptionState_.ServerHandle; + + /// + /// Whether the subscription is active. + /// + public bool Active => subscriptionState_.Active; + + /// + /// Whether data callbacks are enabled. + /// + public bool Enabled => enabled_; + + /// + /// The current locale used by the subscription. + /// + public string Locale => subscriptionState_.Locale; + + /// + /// The current result filters applied by the subscription. + /// + public int Filters => filters_; + + /// + /// Returns a copy of the current subscription state. + /// + public TsCDaSubscriptionState State => (TsCDaSubscriptionState)subscriptionState_.Clone(); + + /// + /// The items belonging to the subscription. + /// + public TsCDaItem[] Items { + get { + if (daItems_ == null) return new TsCDaItem[0]; + var items = new TsCDaItem[daItems_.Length]; + for (var ii = 0; ii < daItems_.Length; ii++) items[ii] = (TsCDaItem)daItems_[ii].Clone(); + return items; + } + } + #endregion + + #region Public Methods + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.State, subscriptionState_); + info.AddValue(Names.Filters, filters_); + info.AddValue(Names.Items, daItems_); + } + + /// + /// Returns an unconnected copy of the subscription with the same items. + /// + public virtual object Clone() + { + // do a memberwise clone. + var clone = (TsCDaSubscription)MemberwiseClone(); + + // place clone in disconnected state. + clone.server_ = null; + clone.Subscription = null; + clone.subscriptionState_ = (TsCDaSubscriptionState)subscriptionState_.Clone(); + + // clear server handles. + clone.subscriptionState_.ServerHandle = null; + + // always make cloned subscriptions inactive. + clone.subscriptionState_.Active = false; + + // clone items. + if (clone.daItems_ != null) + { + var items = new ArrayList(); + + Array.ForEach(clone.daItems_, item => items.Add(item.Clone())); + + clone.daItems_ = (TsCDaItem[])items.ToArray(typeof(TsCDaItem)); + } + + // return clone. + return clone; + } + + /// + /// Gets default result filters for the server. + /// + public int GetResultFilters() + { + filters_ = Subscription.GetResultFilters(); + return filters_; + } + + /// + /// Sets default result filters for the server. + /// + public void SetResultFilters(int filters) + { + Subscription.SetResultFilters(filters); + filters_ = filters; + } + + /// + /// Returns the current subscription state. + /// + public TsCDaSubscriptionState GetState() + { + subscriptionState_ = Subscription.GetState(); + return subscriptionState_; + } + + /// + /// Updates the current subscription state. + /// + public TsCDaSubscriptionState ModifyState(int masks, TsCDaSubscriptionState state) + { + subscriptionState_ = Subscription.ModifyState(masks, state); + return subscriptionState_; + } + + /// + /// Adds items to the subscription. + /// + public virtual TsCDaItemResult[] AddItems(TsCDaItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + if (items == null) throw new ArgumentNullException(nameof(items)); + + // check if there is nothing to do. + if (items.Length == 0) + { + return new TsCDaItemResult[0]; + } + + // add items. + var results = Subscription.AddItems(items); + + if (results == null || results.Length == 0) + { + throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + // update locale item list. + var itemList = new ArrayList(); + if (daItems_ != null) itemList.AddRange(daItems_); + + for (var ii = 0; ii < results.Length; ii++) + { + // check for failure. + if (results[ii].Result.Failed()) + { + continue; + } + + // create locale copy of the item. + // item name, item path and client handle may not be returned by server. + var item = new TsCDaItem(results[ii]) { ItemName = items[ii].ItemName, ItemPath = items[ii].ItemPath, ClientHandle = items[ii].ClientHandle }; + + itemList.Add(item); + } + + // save the new item list. + daItems_ = (TsCDaItem[])itemList.ToArray(typeof(TsCDaItem)); + + // update the local state. + GetState(); + + // return results. + return results; + } + + /// + /// Modifies items that are already part of the subscription. + /// + public virtual TsCDaItemResult[] ModifyItems(int masks, TsCDaItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + if (items == null) throw new ArgumentNullException(nameof(items)); + + // check if there is nothing to do. + if (items.Length == 0) + { + return new TsCDaItemResult[0]; + } + + // modify items. + var results = Subscription.ModifyItems(masks, items); + + if (results == null || results.Length == 0) + { + throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + // update local item - modify item success means all fields were updated successfully. + for (var ii = 0; ii < results.Length; ii++) + { + // check for failure. + if (results[ii].Result.Failed()) + { + continue; + } + + // search local item list. + for (var jj = 0; jj < daItems_.Length; jj++) + { + if (daItems_[jj].ServerHandle.Equals(items[ii].ServerHandle)) + { + // update locale copy of the item. + // item name, item path and client handle may not be returned by server. + var item = new TsCDaItem(results[ii]) { ItemName = daItems_[jj].ItemName, ItemPath = daItems_[jj].ItemPath, ClientHandle = daItems_[jj].ClientHandle }; + + daItems_[jj] = item; + break; + } + } + } + + // update the local state. + GetState(); + + // return results. + return results; + } + + /// + /// Removes items from a subscription. + /// + public virtual OpcItemResult[] RemoveItems(OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + if (items == null) throw new ArgumentNullException(nameof(items)); + + // check if there is nothing to do. + if (items.Length == 0) + { + return new OpcItemResult[0]; + } + + // remove items from server. + var results = Subscription.RemoveItems(items); + + if (results == null || results.Length == 0) + { + throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + // remove items from local list if successful. + var itemList = new ArrayList(); + + foreach (var item in daItems_) + { + var removed = false; + + for (var ii = 0; ii < results.Length; ii++) + { + if (item.ServerHandle.Equals(items[ii].ServerHandle)) + { + removed = results[ii].Result.Succeeded(); + break; + } + } + + if (!removed) itemList.Add(item); + } + + // update local list. + daItems_ = (TsCDaItem[])itemList.ToArray(typeof(TsCDaItem)); + + // update the local state. + GetState(); + + // return results. + return results; + } + + /// + /// Reads a set of subscription items. + /// + public TsCDaItemValueResult[] Read(TsCDaItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + return Subscription.Read(items); + } + + /// + /// Writes a set of subscription items. + /// + public OpcItemResult[] Write(TsCDaItemValue[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + return Subscription.Write(items); + } + + /// + /// Begins an asynchronous read operation for a set of items. + /// + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Read( + TsCDaItem[] items, + object requestHandle, + TsCDaReadCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + return Subscription.Read(items, requestHandle, callback, out request); + } + + /// + /// Begins an asynchronous write operation for a set of items. + /// + /// The set of item values to write (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Write( + TsCDaItemValue[] items, + object requestHandle, + TsCDaWriteCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + return Subscription.Write(items, requestHandle, callback, out request); + } + + /// + /// Cancels an asynchronous request. + /// + public void Cancel(IOpcRequest request, TsCDaCancelCompleteEventHandler callback) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + Subscription.Cancel(request, callback); + } + + /// + /// Tells the server to send an data change update for all subscription items. + /// + public void Refresh() { Subscription.Refresh(); } + + /// + /// Causes the server to send a data changed notification for all active items. + /// + /// An identifier for the request assigned by the caller. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public void Refresh( + object requestHandle, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + Subscription.Refresh(requestHandle, out request); + } + + /// + /// Sets whether data change callbacks are enabled. + /// + public void SetEnabled(bool enabled) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + Subscription.SetEnabled(enabled); + enabled_ = enabled; + } + + /// + /// Gets whether data change callbacks are enabled. + /// + public bool GetEnabled() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.DataAccess); + enabled_ = Subscription.GetEnabled(); + return enabled_; + } + #endregion + + #region ISubscription + /// + /// An event to receive data change updates. + /// + public event TsCDaDataChangedEventHandler DataChangedEvent { + add => Subscription.DataChangedEvent += value; + remove => Subscription.DataChangedEvent -= value; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/SubscriptionCollection.cs b/Technosoftware/DaAeHdaClient/Da/SubscriptionCollection.cs new file mode 100644 index 0000000..f88eccb --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/SubscriptionCollection.cs @@ -0,0 +1,316 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// A collection of subscriptions. + [Serializable] + public class TsCDaSubscriptionCollection : ICollection, ICloneable, IList + { + /////////////////////////////////////////////////////////////////////// + #region Fields + + private ArrayList _subscriptions = new ArrayList(); + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Constructors, Destructor, Initialization + + /// + /// Initializes object with the default values. + /// + public TsCDaSubscriptionCollection() { } + + /// + /// Initializes object with the specified SubscriptionCollection object. + /// + public TsCDaSubscriptionCollection(TsCDaSubscriptionCollection subscriptions) + { + if (subscriptions != null) + { + foreach (TsCDaSubscription subscription in subscriptions) + { + Add(subscription); + } + } + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Properties + + /// + /// Gets the item at the specified index. + /// + public TsCDaSubscription this[int index] + { + get => (TsCDaSubscription)_subscriptions[index]; + set => _subscriptions[index] = value; + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region ICloneable Members + + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCDaSubscriptionCollection)MemberwiseClone(); + + clone._subscriptions = new ArrayList(); + + foreach (TsCDaSubscription subscription in _subscriptions) + { + clone._subscriptions.Add(subscription.Clone()); + } + + return clone; + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region ICollection Members + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => (_subscriptions != null) ? _subscriptions.Count : 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + if (_subscriptions != null) + { + _subscriptions.CopyTo(array, index); + } + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCDaSubscription[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region IEnumerable Members + + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return _subscriptions.GetEnumerator(); + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region IList Members + + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => _subscriptions[index]; + + set + { + if (!typeof(TsCDaSubscription).IsInstanceOfType(value)) + { + throw new ArgumentException("May only add Subscription objects into the collection."); + } + + _subscriptions[index] = value; + } + } + + /// + /// Removes the IList subscription at the specified index. + /// + /// The zero-based index of the subscription to remove. + public void RemoveAt(int index) + { + _subscriptions.RemoveAt(index); + } + + /// + /// Inserts an subscription to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!typeof(TsCDaSubscription).IsInstanceOfType(value)) + { + throw new ArgumentException("May only add Subscription objects into the collection."); + } + + _subscriptions.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + _subscriptions.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return _subscriptions.Contains(value); + } + + /// + /// Removes all subscriptions from the IList. + /// + public void Clear() + { + _subscriptions.Clear(); + } + + /// + /// Determines the index of a specific subscription in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return _subscriptions.IndexOf(value); + } + + /// + /// Adds an subscription to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!typeof(TsCDaSubscription).IsInstanceOfType(value)) + { + throw new ArgumentException("May only add Subscription objects into the collection."); + } + + return _subscriptions.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an subscription to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCDaSubscription value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCDaSubscription value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCDaSubscription value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific subscription in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCDaSubscription value) + { + return IndexOf((object)value); + } + + /// + /// Adds an subscription to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCDaSubscription value) + { + return Add((object)value); + } + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Da/SubscriptionState.cs b/Technosoftware/DaAeHdaClient/Da/SubscriptionState.cs new file mode 100644 index 0000000..7e12d02 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Da/SubscriptionState.cs @@ -0,0 +1,172 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Da +{ + /// + /// Describes the state of a subscription. + /// + [Serializable] + public class TsCDaSubscriptionState : ICloneable + { + #region Fields + private bool active_ = true; + private int updateRate_ = 500; + private float deadband_; + #endregion + + #region Properties + /// + /// A unique name for the subscription controlled by the client. + /// + public string Name { get; set; } + + /// + /// A unique identifier for the subscription assigned by the client. + /// + public object ClientHandle { get; set; } + + /// + /// A unique subscription identifier assigned by the server. + /// + public object ServerHandle { get; set; } + + /// + /// The locale used for any error messages or results returned to the client. + /// + public string Locale { get; set; } + + /// + /// Whether the subscription is scanning for updates to send to the client. + /// + public bool Active + { + get => active_; + set => active_ = value; + } + + /// + /// The rate in milliseconds at which the server checks of updates to send to the + /// client. + /// + /// + /// Client Specifies the fastest rate at which data changes may be sent to the + /// DataChangedHandler + /// for items in this subscription. This also indicates the desired accuracy of Cached + /// Data. This is intended only to control the behavior of the interface. How the + /// server deals with the update rate and how often it actually polls the hardware + /// internally is an implementation detail. Passing 0 indicates the server should use + /// the fastest practical rate. + /// + public int UpdateRate + { + get => updateRate_; + set => updateRate_ = value; + } + + /// The maximum period in milliseconds between updates sent to the client. + /// + /// Clients can set the keep-alive time for a subscription to cause the server to + /// provide client callbacks on the subscription when there are no new events to + /// report. Clients can then be assured of the health of the server and subscription + /// without resorting to pinging the server with calls to GetStatus(). + /// Using this facility, a client can expect a callback (data or keep-alive) + /// within the specified keep-alive time. + /// Servers shall reset their keep-alive timers when real data is sent (i.e. it + /// is not acceptable to constantly send the keep-alive callback at a fixed period + /// equal to the keep-alive time irrespective of data callbacks). + /// + /// The keep-alive callback consists of a call to the + /// DataChangedEventHandler + /// with an empty value list. + /// + /// + /// Keep-alive callbacks will not occur when the subscription is inactive. + /// Keep-alive callbacks do not affect the value of the + /// LastUpdateTime returned by + /// GetServerStatus() . + /// + /// Available only for OPC Data Access 3.0 and OPC XML-DA + /// servers. + /// + public int KeepAlive { get; set; } + + /// + /// The minimum percentage change from 0.0 to 100.0 required to trigger a data update + /// for an item. + /// + /// + /// The range of the Deadband is from 0.0 to 100.0 Percent. Deadband will only + /// apply to items in the subscription that have a dwEUType of Analog available. If the + /// EU Type is Analog, then the EU Low and EU High values for the item can be used to + /// calculate the range for the item. This range will be multiplied with the Deadband + /// to generate an exception limit. An exception is determined as follows: + ///
+ /// Exception if (absolute value of (last cached value - current value) > + /// (pPercentDeadband/100.0) * (EU High - EU Low) ) + ///
+ /// The PercentDeadband can be set when CreateSubscription is called, allowing + /// the same PercentDeadband to be used for all items within that particular + /// subscription. However, with OPC DA 3.0, it is allowable to set the PercentDeadband + /// on a per item basis. This means that each item can potentially override the + /// PercentDeadband set for the subscription it resides within. + /// If the exception limit is exceeded, then the last cached value is updated + /// with the new value and a notification will be sent to the client’s callback (if + /// any). The PercentDeadband is an optional behavior for the server. If the client + /// does not specify this value on a server that does support the behavior, the default + /// value of 0 (zero) will be assumed, and all value changes will update the CACHE. + /// Note that the timestamp will be updated regardless of whether the cached value is + /// updated. A server which does not support deadband should return an error + /// (OPC_E_DEADBANDNOTSUPPORTED) if the client requests a deadband other than + /// 0.0. + /// The UpdateRate for a subscription or the sampling rate of the item, if set, + /// determines time between when a value is checked to see if the exception limit has + /// been exceeded. The PercentDeadband is used to keep noisy signals from updating the + /// client unnecessarily. + ///
+ public float Deadband + { + get => deadband_; + set => deadband_ = value; + } + + /// + /// TimeZone Bias of Group (in minutes). + /// + public int TimeBias { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Aggregate.cs b/Technosoftware/DaAeHdaClient/Hda/Aggregate.cs new file mode 100644 index 0000000..6b4d964 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Aggregate.cs @@ -0,0 +1,70 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The description of an item aggregate supported by the server. + /// + [Serializable] + public class TsCHdaAggregate : ICloneable + { + #region Properties + /// + /// A unique identifier for the aggregate. + /// + public int Id { get; set; } + + /// + /// The unique name for the aggregate. + /// + public string Name { get; set; } + + /// + /// A short description of the aggregate. + /// + public string Description { get; set; } + #endregion + + #region Public Methods + /// + /// Returns a string that represents the current object. + /// + /// + public override string ToString() + { + return Name; + } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() { return MemberwiseClone(); } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AggregateCollection.cs b/Technosoftware/DaAeHdaClient/Hda/AggregateCollection.cs new file mode 100644 index 0000000..9e193bc --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AggregateCollection.cs @@ -0,0 +1,178 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The description of an item aggregate supported by the server. + /// + [Serializable] + public class TsCHdaAggregateCollection : ICloneable, ICollection + { + #region Fields + private TsCHdaAggregate[] hdaAggregates_ = new TsCHdaAggregate[0]; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + public TsCHdaAggregateCollection() + { + // do nothing. + } + + /// + /// Initializes the object with any Aggregates contained in the collection. + /// + /// A collection containing aggregate descriptions. + public TsCHdaAggregateCollection(ICollection collection) + { + Init(collection); + } + #endregion + + #region Properties + /// + /// Returns the aggregate at the specified index. + /// + public TsCHdaAggregate this[int index] + { + get => hdaAggregates_[index]; + set => hdaAggregates_[index] = value; + } + #endregion + + #region Public Methods + /// + /// Returns the first aggregate with the specified id. + /// + public TsCHdaAggregate Find(int id) + { + foreach (var aggregate in hdaAggregates_) + { + if (aggregate.Id == id) + { + return aggregate; + } + } + + return null; + } + + /// + /// Initializes the object with any aggregates contained in the collection. + /// + /// A collection containing aggregate descriptions. + public void Init(ICollection collection) + { + Clear(); + + if (collection != null) + { + var aggregates = new ArrayList(collection.Count); + + foreach (var value in collection) + { + if (value.GetType() == typeof(TsCHdaAggregate)) + { + aggregates.Add(OpcConvert.Clone(value)); + } + } + + hdaAggregates_ = (TsCHdaAggregate[])aggregates.ToArray(typeof(TsCHdaAggregate)); + } + } + + /// + /// Removes all aggregates in the collection. + /// + public void Clear() + { + hdaAggregates_ = new TsCHdaAggregate[0]; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return new TsCHdaAggregateCollection(this); + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => hdaAggregates_?.Length ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + hdaAggregates_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaAggregate[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return hdaAggregates_.GetEnumerator(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AggregateId.cs b/Technosoftware/DaAeHdaClient/Hda/AggregateId.cs new file mode 100644 index 0000000..a8f7461 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AggregateId.cs @@ -0,0 +1,141 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Defines constants for well-known item aggregates. + /// + /// This indicates the aggregate to be used when retrieving processed history. The precise meaning of each aggregate may be server specific. Aggregates not supported by the server shall return E_INVALIDARG in the error code for that aggregate. Additional aggregates may be defined by vendors. Server specific aggregates must be defined with values beginning at 0x80000000. The OPC foundation reserves all aggregates IDs from 0 to 0x7fffffff. + public class TsCHdaAggregateID + { + #region Constants + /// + /// Do not retrieve an aggregate. + /// + public const int NoAggregate = 0; + /// + /// Do not retrieve an aggregate. This is used for retrieving interpolated values. + /// + public const int Interpolative = 1; + /// + /// Retrieve the totalized value (time integral) of the data over the re-sample interval. + /// + public const int Total = 2; + /// + /// Retrieve the average data over the re-sample interval. + /// + public const int Average = 3; + /// + /// Retrieve the time weighted average data over the re-sample interval. + /// + public const int TimeAverage = 4; + /// + /// Retrieve the number of raw values over the re-sample interval. + /// + public const int Count = 5; + /// + /// Retrieve the standard deviation over the re-sample interval. + /// + public const int StandardDeviation = 6; + /// + /// Retrieve the minimum value in the re-sample interval and the timestamp of the minimum value. + /// + public const int MinimumActualTime = 7; + /// + /// Retrieve the minimum value in the re-sample interval. + /// + public const int Minimum = 8; + /// + /// Retrieve the maximum value in the re-sample interval and the timestamp of the maximum value. + /// + public const int MaximumActualTime = 9; + /// + /// Retrieve the maximum value in the re-sample interval. + /// + public const int Maximum = 10; + /// + /// Retrieve the value at the beginning of the re-sample interval. The time stamp is the time stamp of the beginning of the interval. + /// + public const int Start = 11; + /// + /// Retrieve the value at the end of the re-sample interval. The time stamp is the time stamp of the end of the interval. + /// + public const int End = 12; + /// + /// Retrieve the difference between the first and last value in the re-sample interval. + /// + public const int Delta = 13; + /// + /// Retrieve the slope of the regression line over the re-sample interval. + /// + public const int RegSlope = 14; + /// + /// Retrieve the intercept of the regression line over the re-sample interval. This is the value of the regression line at the start of the interval. + /// + public const int RegConst = 15; + /// + /// Retrieve the standard deviation of the regression line over the re-sample interval. + /// + public const int RegDev = 16; + /// + /// Retrieve the variance over the sample interval. + /// + public const int Variance = 17; + /// + /// Retrieve the difference between the minimum and maximum value over the sample interval. + /// + public const int Range = 18; + /// + /// Retrieve the duration (in seconds) of time in the interval during which the data is good. + /// + public const int DurationGood = 19; + /// + /// Retrieve the duration (in seconds) of time in the interval during which the data is bad. + /// + public const int DurationBad = 20; + /// + /// Retrieve the percent of data (1 equals 100 percent) in the interval which has good quality. + /// + public const int PercentGood = 21; + /// + /// Retrieve the percent of data (1 equals 100 percent) in the interval which has bad quality. + /// + public const int PercentBad = 22; + /// + /// Retrieve the worst quality of data in the interval. + /// + public const int WorstQuality = 23; + /// + /// Retrieve the number of annotations in the interval. + /// + public const int Annotations = 24; + #endregion + + #region Constructors, Destructor, Initialization + private TsCHdaAggregateID() { } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AnnotationValue.cs b/Technosoftware/DaAeHdaClient/Hda/AnnotationValue.cs new file mode 100644 index 0000000..98555a6 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AnnotationValue.cs @@ -0,0 +1,84 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// An annotation associated with an item. + /// + [Serializable] + public class TsCHdaAnnotationValue : ICloneable + { + #region Fields + private DateTime timestamp_ = DateTime.MinValue; + private DateTime creationTime_ = DateTime.MinValue; + #endregion + + #region Properties + /// + /// The timestamp for the annotation. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime Timestamp + { + get => timestamp_; + set => timestamp_ = value; + } + + /// + /// The text of the annotation. + /// + public string Annotation { get; set; } + + /// + /// The time when the annotation was created. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime CreationTime + { + get => creationTime_; + set => creationTime_ = value; + } + + /// + /// The user who created the annotation. + /// + public string User { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AnnotationValueCollection.cs b/Technosoftware/DaAeHdaClient/Hda/AnnotationValueCollection.cs new file mode 100644 index 0000000..ac6f0dc --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AnnotationValueCollection.cs @@ -0,0 +1,348 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of item values passed to write or returned from a read operation. + /// + [Serializable] + public class TsCHdaAnnotationValueCollection : TsCHdaItem, IOpcResult, ITsCHdaActualTime, IList + { + #region Fields + private ArrayList values_ = new ArrayList(); + private DateTime startTime_ = DateTime.MinValue; + private DateTime endTime_ = DateTime.MinValue; + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaAnnotationValueCollection() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCHdaAnnotationValueCollection(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified Item object. + /// + public TsCHdaAnnotationValueCollection(TsCHdaItem item) : base(item) { } + + /// + /// Initializes object with the specified ItemValueCollection object. + /// + public TsCHdaAnnotationValueCollection(TsCHdaAnnotationValueCollection item) + : base(item) + { + values_ = new ArrayList(item.values_.Count); + + foreach (TsCHdaItemValue value in item.values_) + { + values_.Add(value.Clone()); + } + } + #endregion + + #region Properties + /// + /// Accessor for elements in the collection. + /// + public TsCHdaAnnotationValue this[int index] + { + get => (TsCHdaAnnotationValue)values_[index]; + set => values_[index] = value; + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region IActualTime Members + /// + /// The actual start time used by a server while processing a request. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime StartTime + { + get => startTime_; + set => startTime_ = value; + } + + /// + /// The actual end time used by a server while processing a request. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime EndTime + { + get => endTime_; + set => endTime_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + var collection = (TsCHdaAnnotationValueCollection)base.Clone(); + + collection.values_ = new ArrayList(values_.Count); + + foreach (TsCHdaAnnotationValue value in values_) + { + collection.values_.Add(value.Clone()); + } + + return collection; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => values_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + values_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaAnnotationValue[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return values_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => values_[index]; + + set + { + if (!(value is TsCHdaAnnotationValue)) + { + throw new ArgumentException("May only add AnnotationValue objects into the collection."); + } + + values_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + values_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is TsCHdaAnnotationValue)) + { + throw new ArgumentException("May only add AnnotationValue objects into the collection."); + } + + values_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + values_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return values_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + values_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return values_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is TsCHdaAnnotationValue)) + { + throw new ArgumentException("May only add AnnotationValue objects into the collection."); + } + + return values_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaAnnotationValue value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaAnnotationValue value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaAnnotationValue value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaAnnotationValue value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaAnnotationValue value) + { + return Add((object)value); + } + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Attribute.cs b/Technosoftware/DaAeHdaClient/Hda/Attribute.cs new file mode 100644 index 0000000..8f860aa --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Attribute.cs @@ -0,0 +1,75 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The description of an item attribute supported by the server. + /// + [Serializable] + public class TsCHdaAttribute : ICloneable + { + #region Properties + /// + /// A unique identifier for the attribute. + /// + public int ID { get; set; } + + /// + /// The unique name for the attribute. + /// + public string Name { get; set; } + + /// + /// A short description of the attribute. + /// + public string Description { get; set; } + + /// + /// The data type of the attribute. + /// + public Type DataType { get; set; } + #endregion + + #region Public Methods + /// + /// Returns a string that represents the current object. + /// + /// + public override string ToString() + { + return Name; + } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() { return MemberwiseClone(); } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AttributeCollection.cs b/Technosoftware/DaAeHdaClient/Hda/AttributeCollection.cs new file mode 100644 index 0000000..4447458 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AttributeCollection.cs @@ -0,0 +1,174 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The description of an item attribute supported by the server. + /// + [Serializable] + public class TsCHdaAttributeCollection : ICloneable, ICollection + { + #region Fields + private TsCHdaAttribute[] hdaAttributes_ = new TsCHdaAttribute[0]; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + public TsCHdaAttributeCollection() { } + + /// + /// Initializes the object with any Attributes contained in the collection. + /// + /// A collection containing attribute descriptions. + public TsCHdaAttributeCollection(ICollection collection) + { + Init(collection); + } + #endregion + + #region Properties + /// + /// Returns the attribute at the specified index. + /// + public TsCHdaAttribute this[int index] + { + get => hdaAttributes_[index]; + set => hdaAttributes_[index] = value; + } + #endregion + + #region Public Methods + /// + /// Returns the first attribute with the specified id. + /// + public TsCHdaAttribute Find(int id) + { + foreach (var attribute in hdaAttributes_) + { + if (attribute.ID == id) + { + return attribute; + } + } + + return null; + } + + /// + /// Initializes the object with any attributes contained in the collection. + /// + /// A collection containing attribute descriptions. + public void Init(ICollection collection) + { + Clear(); + + if (collection != null) + { + var attributes = new ArrayList(collection.Count); + + foreach (var value in collection) + { + if (value.GetType() == typeof(TsCHdaAttribute)) + { + attributes.Add(OpcConvert.Clone(value)); + } + } + hdaAttributes_ = (TsCHdaAttribute[])attributes.ToArray(typeof(TsCHdaAttribute)); + } + } + + /// + /// Removes all attributes in the collection. + /// + public void Clear() + { + hdaAttributes_ = new TsCHdaAttribute[0]; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return new TsCHdaAttributeCollection(this); + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => hdaAttributes_?.Length ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + hdaAttributes_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Attribute[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return hdaAttributes_.GetEnumerator(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AttributeID.cs b/Technosoftware/DaAeHdaClient/Hda/AttributeID.cs new file mode 100644 index 0000000..b979022 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AttributeID.cs @@ -0,0 +1,72 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Defines constants for well-known item attributes. + /// + public class TsCHdaAttributeID + { + /// + public const int DATA_TYPE = 0x01; + /// + public const int DESCRIPTION = 0x02; + /// + public const int ENG_UNITS = 0x03; + /// + public const int STEPPED = 0x04; + /// + public const int ARCHIVING = 0x05; + /// + public const int DERIVE_EQUATION = 0x06; + /// + public const int NODE_NAME = 0x07; + /// + public const int PROCESS_NAME = 0x08; + /// + public const int SOURCE_NAME = 0x09; + /// + public const int SOURCE_TYPE = 0x0a; + /// + public const int NORMAL_MAXIMUM = 0x0b; + /// + public const int NORMAL_MINIMUM = 0x0c; + /// + public const int ITEMID = 0x0d; + /// + public const int MAX_TIME_INT = 0x0e; + /// + public const int MIN_TIME_INT = 0x0f; + /// + public const int EXCEPTION_DEV = 0x10; + /// + public const int EXCEPTION_DEV_TYPE = 0x11; + /// + public const int HIGH_ENTRY_LIMIT = 0x12; + /// + public const int LOW_ENTRY_LIMIT = 0x13; + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AttributeValue.cs b/Technosoftware/DaAeHdaClient/Hda/AttributeValue.cs new file mode 100644 index 0000000..09639c5 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AttributeValue.cs @@ -0,0 +1,69 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The value of an attribute at a point in time. + /// + [Serializable] + public class TsCHdaAttributeValue : ICloneable + { + #region Fields + private DateTime timestamp_ = DateTime.MinValue; + #endregion + + #region Properties + /// + /// The value of the data. + /// + public object Value { get; set; } + + /// + /// The timestamp associated with the value. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime Timestamp + { + get => timestamp_; + set => timestamp_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCHdaAttributeValue)MemberwiseClone(); + clone.Value = OpcConvert.Clone(Value); + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/AttributeValueCollection.cs b/Technosoftware/DaAeHdaClient/Hda/AttributeValueCollection.cs new file mode 100644 index 0000000..8c2af36 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/AttributeValueCollection.cs @@ -0,0 +1,323 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The set of values for an item attribute over a period of time. + /// + [Serializable] + public class TsCHdaAttributeValueCollection : IOpcResult, ICollection, ICloneable, IList + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + private ArrayList attributeValues_ = new ArrayList(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaAttributeValueCollection() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCHdaAttributeValueCollection(TsCHdaAttribute attribute) + { + AttributeID = attribute.ID; + } + + /// + /// Initializes object with the specified AttributeValueCollection object. + /// + public TsCHdaAttributeValueCollection(TsCHdaAttributeValueCollection collection) + { + attributeValues_ = new ArrayList(collection.attributeValues_.Count); + + foreach (TsCHdaAttributeValue value in collection.attributeValues_) + { + attributeValues_.Add(value.Clone()); + } + } + #endregion + + #region Properties + /// + /// A unique identifier for the attribute. + /// + public int AttributeID { get; set; } + + /// + /// Accessor for elements in the collection. + /// + public TsCHdaAttributeValue this[int index] + { + get => (TsCHdaAttributeValue)attributeValues_[index]; + set => attributeValues_[index] = value; + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var collection = (TsCHdaAttributeValueCollection)MemberwiseClone(); + + collection.attributeValues_ = new ArrayList(attributeValues_.Count); + + foreach (TsCHdaAttributeValue value in attributeValues_) + { + collection.attributeValues_.Add(value.Clone()); + } + + return collection; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => attributeValues_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + attributeValues_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaAttributeValue[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return attributeValues_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => attributeValues_[index]; + + set + { + if (!(value is TsCHdaAttributeValue)) + { + throw new ArgumentException("May only add AttributeValue objects into the collection."); + } + + attributeValues_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + attributeValues_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is TsCHdaAttributeValue)) + { + throw new ArgumentException("May only add AttributeValue objects into the collection."); + } + + attributeValues_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + attributeValues_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return attributeValues_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + attributeValues_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return attributeValues_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is TsCHdaAttributeValue)) + { + throw new ArgumentException("May only add AttributeValue objects into the collection."); + } + + return attributeValues_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaAttributeValue value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaAttributeValue value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaAttributeValue value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaAttributeValue value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaAttributeValue value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/BrowseElement.cs b/Technosoftware/DaAeHdaClient/Hda/BrowseElement.cs new file mode 100644 index 0000000..d6a1aa1 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/BrowseElement.cs @@ -0,0 +1,75 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Contains the description of an element in the server's address space. + /// + public class TsCHdaBrowseElement : OpcItem + { + #region Fields + private TsCHdaAttributeValueCollection attributes_ = new TsCHdaAttributeValueCollection(); + #endregion + + #region Properties + /// + /// The name of element within its branch. + /// + public string Name { get; set; } + + /// + /// Whether the element is an item with associated data in the archive. + /// + public bool IsItem { get; set; } + + /// + /// Whether the element has child elements. + /// + public bool HasChildren { get; set; } + + /// + /// The current values of any attributes associated with the item. + /// + public TsCHdaAttributeValueCollection Attributes + { + get => attributes_; + set => attributes_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep-copy of the object. + /// + public override object Clone() + { + var element = (TsCHdaBrowseElement)MemberwiseClone(); + element.Attributes = (TsCHdaAttributeValueCollection)attributes_.Clone(); + return element; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/BrowseFilter.cs b/Technosoftware/DaAeHdaClient/Hda/BrowseFilter.cs new file mode 100644 index 0000000..25ea1fa --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/BrowseFilter.cs @@ -0,0 +1,72 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Defines a filter to apply to an item attribute when browsing. + /// + [Serializable] + public class TsCHdaBrowseFilter : ICloneable + { + #region Fields + private TsCHdaOperator filterOperator_ = TsCHdaOperator.Equal; + #endregion + + #region Properties + /// + /// The attribute id to use when filtering. + /// + public int AttributeID { get; set; } + + /// + /// The operator to use when testing if the filter condition is met. + /// + public TsCHdaOperator Operator + { + get => filterOperator_; + set => filterOperator_ = value; + } + + /// + /// The value of the filter. The '*' and '?' wildcard characters are permitted. + /// + public object FilterValue { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep-copy of the object. + /// + public virtual object Clone() + { + var filter = (TsCHdaBrowseFilter)MemberwiseClone(); + filter.FilterValue = OpcConvert.Clone(FilterValue); + return filter; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/BrowseFilterCollection.cs b/Technosoftware/DaAeHdaClient/Hda/BrowseFilterCollection.cs new file mode 100644 index 0000000..5abf73f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/BrowseFilterCollection.cs @@ -0,0 +1,178 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of attribute filters used when browsing the server address space. + /// + [Serializable] + public class TsCHdaBrowseFilterCollection : OpcItem, ICollection + { + #region Fields + private TsCHdaBrowseFilter[] browseFilters_ = new TsCHdaBrowseFilter[0]; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Creates an empty collection. + /// + public TsCHdaBrowseFilterCollection() + { + // do nothing. + } + + /// + /// Initializes the object with any BrowseFilter contained in the collection. + /// + /// A collection containing browse filters. + public TsCHdaBrowseFilterCollection(ICollection collection) + { + Init(collection); + } + #endregion + + #region Properties + /// + /// Returns the browse filter at the specified index. + /// + public TsCHdaBrowseFilter this[int index] + { + get => browseFilters_[index]; + set => browseFilters_[index] = value; + } + #endregion + + #region Public Methods + /// + /// Returns the browse filter for the specified attribute id. + /// + public TsCHdaBrowseFilter Find(int id) + { + foreach (var filter in browseFilters_) + { + if (filter.AttributeID == id) + { + return filter; + } + } + + return null; + } + + /// + /// Initializes the object with any attribute values contained in the collection. + /// + /// A collection containing attribute values. + public void Init(ICollection collection) + { + Clear(); + + if (collection != null) + { + var values = new ArrayList(collection.Count); + + foreach (var value in collection) + { + if (value.GetType() == typeof(TsCHdaBrowseFilter)) + { + values.Add(OpcConvert.Clone(value)); + } + } + + browseFilters_ = (TsCHdaBrowseFilter[])values.ToArray(typeof(TsCHdaBrowseFilter)); + } + } + + /// + /// Removes all attribute values in the collection. + /// + public void Clear() + { + browseFilters_ = new TsCHdaBrowseFilter[0]; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + return new TsCHdaBrowseFilterCollection(this); + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => browseFilters_?.Length ?? 0; + + /// + /// Copies the objects in to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + browseFilters_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaBrowseFilter[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return browseFilters_.GetEnumerator(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/BrowsePosition.cs b/Technosoftware/DaAeHdaClient/Hda/BrowsePosition.cs new file mode 100644 index 0000000..4931ca7 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/BrowsePosition.cs @@ -0,0 +1,52 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Stores the state of a browse operation. + /// + [Serializable] + public class TsCHdaBrowsePosition : IOpcBrowsePosition + { + #region IDisposable Members + /// + /// Releases any unmanaged resources held by the object. + /// + public virtual void Dispose() { } + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() + { + return (TsCHdaBrowsePosition)MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/EditType.cs b/Technosoftware/DaAeHdaClient/Hda/EditType.cs new file mode 100644 index 0000000..688324e --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/EditType.cs @@ -0,0 +1,53 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The types of modifications that can be applied to an item. + /// + public enum TsCHdaEditType + { + /// + /// The item was inserted. + /// + Insert = 1, + + /// + /// The item was replaced. + /// + Replace = 2, + + /// + /// The item was inserted or replaced during an insert/replace operation. + /// + InsertReplace = 3, + + /// + /// The item was deleted. + /// + Delete = 4 + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/IActualTime.cs b/Technosoftware/DaAeHdaClient/Hda/IActualTime.cs new file mode 100644 index 0000000..c835d7d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/IActualTime.cs @@ -0,0 +1,44 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A interface used to actual time information associated with a result. + /// + public interface ITsCHdaActualTime + { + /// + /// The actual start time used by a server while processing a request. + /// + DateTime StartTime { get; set; } + + /// + /// The actual end time used by a server while processing a request. + /// + DateTime EndTime { get; set; } + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/IBrowser.cs b/Technosoftware/DaAeHdaClient/Hda/IBrowser.cs new file mode 100644 index 0000000..56b0e5f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/IBrowser.cs @@ -0,0 +1,63 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Defines functionality that is common to all OPC Data Access servers. + /// + public interface ITsCHdaBrowser : IDisposable + { + /// + /// Returns the set of attribute filters used by the browser. + /// + TsCHdaBrowseFilterCollection Filters { get; } + + /// + /// Browses the server's address space at the specified branch. + /// + /// The item id of the branch to search. + /// The set of elements that meet the filter criteria. + TsCHdaBrowseElement[] Browse(OpcItem itemId); + + /// + /// Begins a browsing the server's address space at the specified branch. + /// + /// The item id of the branch to search. + /// The maximum number of elements to return. + /// The position object used to continue a browse operation. + /// The set of elements that meet the filter criteria. + TsCHdaBrowseElement[] Browse(OpcItem itemId, int maxElements, out IOpcBrowsePosition position); + + /// + /// Continues browsing the server's address space at the specified position. + /// + /// The maximum number of elements to return. + /// The position object used to continue a browse operation. + /// The set of elements that meet the filter criteria. + TsCHdaBrowseElement[] BrowseNext(int maxElements, ref IOpcBrowsePosition position); + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/IServer.cs b/Technosoftware/DaAeHdaClient/Hda/IServer.cs new file mode 100644 index 0000000..125e284 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/IServer.cs @@ -0,0 +1,546 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Defines functionality that is common to all OPC Historical Data Access servers. + /// + public interface ITsCHdaServer : IOpcServer + { + /// + /// Returns the current server status. + /// + /// The current server status. + OpcServerStatus GetServerStatus(); + + /// + /// Returns the item attributes supported by the server. + /// + /// The a set of item attributes and their descriptions. + TsCHdaAttribute[] GetAttributes(); + + /// + /// Returns the aggregates supported by the server. + /// + /// The a set of aggregates and their descriptions. + TsCHdaAggregate[] GetAggregates(); + + /// + /// Creates a object used to browse the server address space. + /// + /// The set of attribute filters to use when browsing. + /// A result code for each individual filter. + /// A browser object that must be released by calling Dispose(). + ITsCHdaBrowser CreateBrowser(TsCHdaBrowseFilter[] filters, out OpcResult[] results); + + /// + /// Creates a set of items. + /// + /// The identifiers for the items to create. + /// The results for each item containing the server handle and result code. + OpcItemResult[] CreateItems(OpcItem[] items); + + /// + /// Releases a set of previously created items. + /// + /// The server handles for the items to release. + /// The results for each item containing the result code. + OpcItemResult[] ReleaseItems(OpcItem[] items); + + /// + /// Validates a set of items. + /// + /// The identifiers for the items to validate. + /// The results for each item containing the result code. + OpcItemResult[] ValidateItems(OpcItem[] items); + + /// + /// Reads raw (unprocessed) data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// Whether the bounding item values should be returned. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + TsCHdaItemValueCollection[] ReadRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + bool includeBounds, + OpcItem[] items); + + /// + /// Sends an asynchronous request to read raw data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// Whether the bounding item values should be returned. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] ReadRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + bool includeBounds, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Requests that the server periodically send notifications when new data becomes available for a set of items. + /// + /// The beginning of the history period to read. + /// The frequency, in seconds, that the server should check for new data. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] AdviseRaw( + TsCHdaTime startTime, + decimal updateInterval, + OpcItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request); + + /// + /// Begins the playback raw data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The frequency, in seconds, that the server send data. + /// The duration, in seconds, of the timespan returned with each update. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] PlaybackRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + decimal updateInterval, + decimal playbackDuration, + OpcItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request); + + /// + /// Reads processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + TsCHdaItemValueCollection[] ReadProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + TsCHdaItem[] items); + + /// + /// Sends an asynchronous request to read processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] ReadProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Requests that the server periodically send notifications when new data becomes available for a set of items. + /// + /// The beginning of the history period to read. + /// The interval between returned values. + /// The number of resample intervals that the server should return in each callback. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] AdviseProcessed( + TsCHdaTime startTime, + decimal resampleInterval, + int numberOfIntervals, + TsCHdaItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request); + + /// + /// Begins the playback of processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The number of resample intervals that the server should return in each callback. + /// The frequency, in seconds, that the server send data. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] PlaybackProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + int numberOfIntervals, + decimal updateInterval, + TsCHdaItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request); + + /// + /// Reads data from the historian database for a set of items at specific times. + /// + /// The set of timestamps to use when reading items values. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + TsCHdaItemValueCollection[] ReadAtTime(DateTime[] timestamps, OpcItem[] items); + + /// + /// Sends an asynchronous request to read item values at specific times. + /// + /// The set of timestamps to use when reading items values. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] ReadAtTime( + DateTime[] timestamps, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request); + + + /// + /// Reads item values that have been deleted or replaced. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + TsCHdaModifiedValueCollection[] ReadModified( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + OpcItem[] items); + + /// + /// Sends an asynchronous request to read item values that have been deleted or replaced. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] ReadModified( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Reads the current or historical values for the attributes of an item. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The item to read (must include the item name). + /// The attributes to read. + /// A set of attribute values for each requested attribute. + TsCHdaItemAttributeCollection ReadAttributes( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem item, + int[] attributeIDs); + + /// + /// Sends an asynchronous request to read the attributes of an item. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The item to read (must include the item name). + /// The attributes to read. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the attribute ids. + TsCHdaResultCollection ReadAttributes( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem item, + int[] attributeIDs, + object requestHandle, + TsCHdaReadAttributesCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Reads any annotations for an item within the a time interval. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The set of items to read (must include the item name). + /// A set of annotations within the requested time range for each item. + TsCHdaAnnotationValueCollection[] ReadAnnotations( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items); + + /// + /// Sends an asynchronous request to read the annotations for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] ReadAnnotations( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items, + object requestHandle, + TsCHdaReadAnnotationsCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Inserts annotations for one or more items. + /// + /// A list of annotations to add for each item (must include the item name). + /// The results of the insert operation for each annotation set. + TsCHdaResultCollection[] InsertAnnotations(TsCHdaAnnotationValueCollection[] items); + + /// + /// Sends an asynchronous request to inserts annotations for one or more items. + /// + /// A list of annotations to add for each item (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] InsertAnnotations( + TsCHdaAnnotationValueCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Inserts the values into the history database for one or more items. + /// + /// The set of values to insert. + /// Whether existing values should be replaced. + /// + TsCHdaResultCollection[] Insert(TsCHdaItemValueCollection[] items, bool replace); + + /// + /// Sends an asynchronous request to inserts values for one or more items. + /// + /// The set of values to insert. + /// Whether existing values should be replaced. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] Insert( + TsCHdaItemValueCollection[] items, + bool replace, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Replace the values into the history database for one or more items. + /// + /// The set of values to replace. + /// + TsCHdaResultCollection[] Replace(TsCHdaItemValueCollection[] items); + + /// + /// Sends an asynchronous request to replace values for one or more items. + /// + /// The set of values to replace. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] Replace( + TsCHdaItemValueCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Deletes the values with the specified time domain for one or more items. + /// + /// The beginning of the history period to delete. + /// The end of the history period to be delete. + /// The set of items to delete (must include the item name). + /// The results of the delete operation for each item. + OpcItemResult[] Delete( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items); + + /// + /// Sends an asynchronous request to delete values for one or more items. + /// + /// The beginning of the history period to delete. + /// The end of the history period to be delete. + /// The set of items to delete (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] Delete( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Deletes the values at the specified times for one or more items. + /// + /// The set of timestamps to delete for one or more items. + /// The results of the operation for each timestamp. + TsCHdaResultCollection[] DeleteAtTime(TsCHdaItemTimeCollection[] items); + + /// + /// Sends an asynchronous request to delete values for one or more items at a specified times. + /// + /// The set of timestamps to delete for one or more items. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + OpcItemResult[] DeleteAtTime( + TsCHdaItemTimeCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request); + + /// + /// Cancels an asynchronous request. + /// + /// The state object for the request to cancel. + void CancelRequest(IOpcRequest request); + + /// + /// Cancels an asynchronous request. + /// + /// The state object for the request to cancel. + /// A delegate used to receive notifications when the request completes. + void CancelRequest(IOpcRequest request, TsCHdaCancelCompleteEventHandler callback); + + } + + #region Delegate Declarations + + /// + /// Used to receive notifications when an exception occurs while processing a callback. + /// + /// An identifier for the request assigned by the caller. + /// Exception which occured. + public delegate void TsCHdaCallbackExceptionEventHandler(IOpcRequest request, Exception exception); + + /// + /// Used to receive data update notifications. + /// + /// An identifier for the request assigned by the caller. + /// A collection of results. + public delegate void TsCHdaDataUpdateEventHandler(IOpcRequest request, TsCHdaItemValueCollection[] results); + + /// + /// Used to receive notifications when a read values request completes. + /// + /// An identifier for the request assigned by the caller. + /// A collection of results. + public delegate void TsCHdaReadValuesCompleteEventHandler(IOpcRequest request, TsCHdaItemValueCollection[] results); + + /// + /// Used to receive notifications when a read attributes request completes. + /// + /// An identifier for the request assigned by the caller. + /// A collection of results. + public delegate void TsCHdaReadAttributesCompleteEventHandler(IOpcRequest request, TsCHdaItemAttributeCollection results); + + /// + /// Used to receive notifications when a read annotations request completes. + /// + /// An identifier for the request assigned by the caller. + /// A collection of results. + public delegate void TsCHdaReadAnnotationsCompleteEventHandler(IOpcRequest request, TsCHdaAnnotationValueCollection[] results); + + /// + /// Used to receive notifications when an update request completes. + /// + /// An identifier for the request assigned by the caller. + /// A collection of results. + public delegate void TsCHdaUpdateCompleteEventHandler(IOpcRequest request, TsCHdaResultCollection[] results); + + /// + /// Used to receive notifications when a request is cancelled. + /// + /// An identifier for the request assigned by the caller. + public delegate void TsCHdaCancelCompleteEventHandler(IOpcRequest request); + + #endregion + +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Item.cs b/Technosoftware/DaAeHdaClient/Hda/Item.cs new file mode 100644 index 0000000..e806cf4 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Item.cs @@ -0,0 +1,74 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Describes an item used in a request for processed or raw data. + /// + [Serializable] + public class TsCHdaItem : OpcItem + { + #region Fields + private int aggregate_ = TsCHdaAggregateID.NoAggregate; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaItem() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCHdaItem(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified Item object. + /// + public TsCHdaItem(TsCHdaItem item) + : base(item) + { + if (item != null) + { + Aggregate = item.Aggregate; + } + } + #endregion + + #region Properties + /// + /// The aggregate to use to process the data. + /// + public int Aggregate + { + get => aggregate_; + set => aggregate_ = value; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ItemAttributeCollection.cs b/Technosoftware/DaAeHdaClient/Hda/ItemAttributeCollection.cs new file mode 100644 index 0000000..57d6887 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ItemAttributeCollection.cs @@ -0,0 +1,345 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of item attribute values passed to write or returned from a read operation. + /// + [Serializable] + public class TsCHdaItemAttributeCollection : OpcItem, IOpcResult, ITsCHdaActualTime, IList + { + #region Fields + private DateTime startTime_ = DateTime.MinValue; + private DateTime endTime_ = DateTime.MinValue; + private ArrayList attributes_ = new ArrayList(); + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaItemAttributeCollection() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCHdaItemAttributeCollection(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified ItemAttributeCollection object. + /// + public TsCHdaItemAttributeCollection(TsCHdaItemAttributeCollection item) + : base(item) + { + attributes_ = new ArrayList(item.attributes_.Count); + + foreach (TsCHdaAttributeValueCollection value in item.attributes_) + { + if (value != null) + { + attributes_.Add(value.Clone()); + } + } + } + #endregion + + #region Properties + /// + /// Accessor for elements in the collection. + /// + public TsCHdaAttributeValueCollection this[int index] + { + get => (TsCHdaAttributeValueCollection)attributes_[index]; + set => attributes_[index] = value; + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region IActualTime Members + /// + /// The actual start time used by a server while processing a request. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime StartTime + { + get => startTime_; + set => startTime_ = value; + } + + /// + /// The actual end time used by a server while processing a request. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime EndTime + { + get => endTime_; + set => endTime_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + var collection = (TsCHdaItemAttributeCollection)base.Clone(); + + collection.attributes_ = new ArrayList(attributes_.Count); + + foreach (TsCHdaAttributeValueCollection value in attributes_) + { + collection.attributes_.Add(value.Clone()); + } + + return collection; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => attributes_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + attributes_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaAttributeValueCollection[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return attributes_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => attributes_[index]; + + set + { + if (!(value is TsCHdaAttributeValueCollection)) + { + throw new ArgumentException("May only add AttributeValueCollection objects into the collection."); + } + + attributes_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + attributes_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is TsCHdaAttributeValueCollection)) + { + throw new ArgumentException("May only add AttributeValueCollection objects into the collection."); + } + + attributes_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + attributes_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return attributes_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + attributes_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return attributes_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is TsCHdaAttributeValueCollection)) + { + throw new ArgumentException("May only add AttributeValueCollection objects into the collection."); + } + + return attributes_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaAttributeValueCollection value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaAttributeValueCollection value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaAttributeValueCollection value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaAttributeValueCollection value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaAttributeValueCollection value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ItemCollection.cs b/Technosoftware/DaAeHdaClient/Hda/ItemCollection.cs new file mode 100644 index 0000000..c6cece6 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ItemCollection.cs @@ -0,0 +1,315 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of items. + /// + [Serializable] + public class TsCHdaItemCollection : ICloneable, IList + { + #region Fields + private ArrayList items_ = new ArrayList(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaItemCollection() { } + + /// + /// Initializes object with the specified ResultCollection object. + /// + public TsCHdaItemCollection(TsCHdaItemCollection items) + { + if (items == null) + { + return; + } + foreach (TsCHdaItem item in items) + { + Add(item); + } + } + #endregion + + #region Properties + + /// + /// Gets the item at the specified index. + /// + public TsCHdaItem this[int index] + { + get => (TsCHdaItem)items_[index]; + set => items_[index] = value; + } + + /// + /// Gets the first item with the specified item id. + /// + public TsCHdaItem this[OpcItem itemId] + { + get + { + foreach (TsCHdaItem item in items_) + { + if (itemId.Key == item.Key) + { + return item; + } + } + + return null; + } + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCHdaItemCollection)MemberwiseClone(); + + clone.items_ = new ArrayList(); + + foreach (TsCHdaItem item in items_) + { + clone.items_.Add(item.Clone()); + } + + return clone; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => items_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + items_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaItem[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return items_.GetEnumerator(); + } + #endregion + + #region IList Members + + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => items_[index]; + + set + { + if (!(value is TsCHdaItem)) + { + throw new ArgumentException("May only add Item objects into the collection."); + } + + items_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + items_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is TsCHdaItem)) + { + throw new ArgumentException("May only add Item objects into the collection."); + } + + items_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + items_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return items_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + items_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return items_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is TsCHdaItem)) + { + throw new ArgumentException("May only add Item objects into the collection."); + } + + return items_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaItem value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaItem value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaItem value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaItem value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaItem value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ItemResult.cs b/Technosoftware/DaAeHdaClient/Hda/ItemResult.cs new file mode 100644 index 0000000..eba158d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ItemResult.cs @@ -0,0 +1,85 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Describes the results for an item used in a read processed or raw data request. + /// + [Serializable] + public class TsCHdaItemResult : TsCHdaItem, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initialize object with default values. + /// + public TsCHdaItemResult() { } + + /// + /// Initialize object with the specified ItemIdentifier object. + /// + public TsCHdaItemResult(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified Item object. + /// + public TsCHdaItemResult(TsCHdaItem item) : base(item) { } + + /// + /// Initialize object with the specified ItemResult object. + /// + public TsCHdaItemResult(TsCHdaItemResult item) + : base(item) + { + if (item != null) + { + Result = item.Result; + DiagnosticInfo = item.DiagnosticInfo; + } + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ItemTimeCollection.cs b/Technosoftware/DaAeHdaClient/Hda/ItemTimeCollection.cs new file mode 100644 index 0000000..e249f8d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ItemTimeCollection.cs @@ -0,0 +1,299 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of results passed to write or returned from an insert, replace or delete operation. + /// + [Serializable] + public class TsCHdaItemTimeCollection : OpcItem, IList + { + #region Fields + private ArrayList times_ = new ArrayList(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaItemTimeCollection() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCHdaItemTimeCollection(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified ItemTimeCollection object. + /// + public TsCHdaItemTimeCollection(TsCHdaItemTimeCollection item) + : base(item) + { + times_ = new ArrayList(item.times_.Count); + + foreach (DateTime value in item.times_) + { + times_.Add(value); + } + } + #endregion + + #region Properties + /// + /// Accessor for elements in the collection. + /// + public DateTime this[int index] + { + get => (DateTime)times_[index]; + set => times_[index] = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + var collection = (TsCHdaItemTimeCollection)base.Clone(); + + collection.times_ = new ArrayList(times_.Count); + + foreach (DateTime value in times_) + { + collection.times_.Add(value); + } + + return collection; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => times_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + times_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(DateTime[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return times_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => times_[index]; + + set + { + if (!(value is DateTime)) + { + throw new ArgumentException("May only add DateTime objects into the collection."); + } + + times_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + times_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is DateTime)) + { + throw new ArgumentException("May only add DateTime objects into the collection."); + } + + times_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + times_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return times_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + times_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return times_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is DateTime)) + { + throw new ArgumentException("May only add DateTime objects into the collection."); + } + + return times_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, DateTime value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(DateTime value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(DateTime value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(DateTime value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(DateTime value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ItemValue.cs b/Technosoftware/DaAeHdaClient/Hda/ItemValue.cs new file mode 100644 index 0000000..6780fd3 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ItemValue.cs @@ -0,0 +1,93 @@ +#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.ComponentModel; +using System.Runtime.InteropServices; +#pragma warning disable 618 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A value of an item at in instant of time. + /// + [Serializable] + public class TsCHdaItemValue : ICloneable + { + #region Fields + private DateTime timestamp_ = DateTime.MinValue; + private Da.TsCDaQuality daQuality_ = Da.TsCDaQuality.Bad; + private TsCHdaQuality historianQuality_ = TsCHdaQuality.NoData; + #endregion + + #region Properties + /// + /// The value of the item. + /// + public object Value { get; set; } + + /// + /// The timestamp associated with the value. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime Timestamp + { + get => timestamp_; + set => timestamp_ = value; + } + + /// + /// The quality associated with the value when it was acquired from the data source. + /// + public Da.TsCDaQuality Quality + { + get => daQuality_; + set => daQuality_ = value; + } + + /// + /// The quality associated with the value when it was retrieved from the hiatorian database. + /// + public TsCHdaQuality HistorianQuality + { + get => historianQuality_; + set => historianQuality_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public object Clone() + { + var value = (TsCHdaItemValue)MemberwiseClone(); + value.Value = OpcConvert.Clone(Value); + return value; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ItemValueCollection.cs b/Technosoftware/DaAeHdaClient/Hda/ItemValueCollection.cs new file mode 100644 index 0000000..f3808ef --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ItemValueCollection.cs @@ -0,0 +1,369 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of item values passed to write or returned from a read operation. + /// + [Serializable] + public class TsCHdaItemValueCollection : TsCHdaItem, IOpcResult, ITsCHdaActualTime, ICollection, ICloneable, IList + { + #region Fields + private DateTime startTime_ = DateTime.MinValue; + private DateTime endTime_ = DateTime.MinValue; + private ArrayList values_ = new ArrayList(); + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaItemValueCollection() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCHdaItemValueCollection(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified Item object. + /// + public TsCHdaItemValueCollection(TsCHdaItem item) : base(item) { } + + /// + /// Initializes object with the specified ItemValueCollection object. + /// + public TsCHdaItemValueCollection(TsCHdaItemValueCollection item) + : base(item) + { + values_ = new ArrayList(item.values_.Count); + + foreach (TsCHdaItemValue value in item.values_) + { + if (value != null) + { + values_.Add(value.Clone()); + } + } + } + #endregion + + #region Properties + /// + /// Accessor for elements in the collection. + /// + public TsCHdaItemValue this[int index] + { + get => (TsCHdaItemValue)values_[index]; + set => values_[index] = value; + } + #endregion + + #region Public Methods + /// + /// Appends another value collection to the collection. + /// + public void AddRange(TsCHdaItemValueCollection collection) + { + if (collection != null) + { + foreach (TsCHdaItemValue value in collection) + { + if (value != null) + { + values_.Add(value.Clone()); + } + } + } + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region IActualTime Members + /// + /// The actual start time used by a server while processing a request. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime StartTime + { + get => startTime_; + set => startTime_ = value; + } + + /// + /// The actual end time used by a server while processing a request. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime EndTime + { + get => endTime_; + set => endTime_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + var collection = (TsCHdaItemValueCollection)base.Clone(); + + collection.values_ = new ArrayList(values_.Count); + + foreach (TsCHdaItemValue value in values_) + { + collection.values_.Add(value.Clone()); + } + + return collection; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => values_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + values_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaItemValue[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return values_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => values_[index]; + + set + { + if (!(value is TsCHdaItemValue)) + { + throw new ArgumentException("May only add ItemValue objects into the collection."); + } + + values_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + values_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is TsCHdaItemValue)) + { + throw new ArgumentException("May only add ItemValue objects into the collection."); + } + + values_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + values_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return values_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + values_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return values_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is TsCHdaItemValue)) + { + throw new ArgumentException("May only add ItemValue objects into the collection."); + } + + return values_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaItemValue value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaItemValue value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaItemValue value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaItemValue value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaItemValue value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ItemValueResult.cs b/Technosoftware/DaAeHdaClient/Hda/ItemValueResult.cs new file mode 100644 index 0000000..753fd14 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ItemValueResult.cs @@ -0,0 +1,90 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A result associated with a single item value. + /// + [Serializable] + public class TsCHdaResult : ICloneable, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public TsCHdaResult() { } + + /// + /// Initializes the object with the specified result id. + /// + public TsCHdaResult(OpcResult resultId) + { + Result = resultId; + DiagnosticInfo = null; + } + + /// + /// Initializes the object with the specified result object. + /// + public TsCHdaResult(IOpcResult result) + { + Result = result.Result; + DiagnosticInfo = result.DiagnosticInfo; + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo { get; set; } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public object Clone() + { + return MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ModifiedValue.cs b/Technosoftware/DaAeHdaClient/Hda/ModifiedValue.cs new file mode 100644 index 0000000..7f55af2 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ModifiedValue.cs @@ -0,0 +1,67 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A value of an item at in instant of time that has be deleted or replaced. + /// + [Serializable] + public class TsCHdaModifiedValue : TsCHdaItemValue + { + #region Fields + private DateTime modificationTime_ = DateTime.MinValue; + private TsCHdaEditType editType_ = TsCHdaEditType.Insert; + #endregion + + #region Properties + /// + /// The time when the value was deleted or replaced. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime ModificationTime + { + get => modificationTime_; + set => modificationTime_ = value; + } + + /// + /// Whether the value was deleted or replaced. + /// + public TsCHdaEditType EditType + { + get => editType_; + set => editType_ = value; + } + + /// + /// The user who modified the item value. + /// + public string User { get; set; } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ModifiedValueCollection.cs b/Technosoftware/DaAeHdaClient/Hda/ModifiedValueCollection.cs new file mode 100644 index 0000000..34661f2 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ModifiedValueCollection.cs @@ -0,0 +1,68 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of modified item values with a result code indicating the results of a read operation. + /// + [Serializable] + public class TsCHdaModifiedValueCollection : TsCHdaItemValueCollection + { + #region Constructors, Destructor, Initialization + /// + /// Initialize object with default values. + /// + public TsCHdaModifiedValueCollection() { } + + /// + /// Initialize object with the specified ItemIdentifier object. + /// + public TsCHdaModifiedValueCollection(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified Item object. + /// + public TsCHdaModifiedValueCollection(TsCHdaItem item) : base(item) { } + + /// + /// Initializes object with the specified ItemValueCollection object. + /// + public TsCHdaModifiedValueCollection(TsCHdaItemValueCollection item) : base(item) { } + #endregion + + #region Properties + /// + /// Accessor for elements in the collection. + /// + public new TsCHdaModifiedValue this[int index] + { + get => (TsCHdaModifiedValue)this[index]; + set => this[index] = value; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Operator.cs b/Technosoftware/DaAeHdaClient/Hda/Operator.cs new file mode 100644 index 0000000..db90503 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Operator.cs @@ -0,0 +1,64 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The set of possible operators to use when applying an item attribute filter. + /// + public enum TsCHdaOperator + { + /// + /// The attribute value is equal (or matches) to the filter. + /// + Equal = 1, + + /// + /// The attribute value is less than the filter. + /// + Less, + + /// + /// The attribute value is less than or equal to the filter. + /// + LessEqual, + + /// + /// The attribute value is greater than the filter. + /// + Greater, + + /// + /// The attribute value is greater than or equal to the filter. + /// + GreaterEqual, + + /// + /// The attribute value is not equal (or does not match)to the filter. + /// + NotEqual + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Quality.cs b/Technosoftware/DaAeHdaClient/Hda/Quality.cs new file mode 100644 index 0000000..1b564ee --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Quality.cs @@ -0,0 +1,80 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Defines possible HDA quality codes. + /// + [Flags] + public enum TsCHdaQuality + { + /// + /// More than one piece of data that may be hidden exists at same timestamp. + /// + ExtraData = 0x00010000, + + /// + /// Interpolated data value. + /// + Interpolated = 0x00020000, + + /// + /// Raw data + /// + Raw = 0x00040000, + + /// + /// Calculated data value, as would be returned from a ReadProcessed call. + /// + Calculated = 0x00080000, + + /// + /// No data found to provide upper or lower bound value. + /// + NoBound = 0x00100000, + + /// + /// Bad No data collected. Archiving not active (for item or all items). + /// + NoData = 0x00200000, + + /// + /// Collection started/stopped/lost. + /// + DataLost = 0x00400000, + + /// + /// Scaling or conversion error. + /// + Conversion = 0x00800000, + + /// + /// Aggregate value is for an incomplete interval. + /// + Partial = 0x01000000 + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/RelativeTime.cs b/Technosoftware/DaAeHdaClient/Hda/RelativeTime.cs new file mode 100644 index 0000000..97dfa9d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/RelativeTime.cs @@ -0,0 +1,74 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Possible base or offset types for relative times. + /// + public enum TsCHdaRelativeTime + { + /// + /// Start from the current time. + /// + Now, + + /// + /// The start of the current second or an offset in seconds. + /// + Second, + + /// + /// The start of the current minutes or an offset in minutes. + /// + Minute, + + /// + /// The start of the current hour or an offset in hours. + /// + Hour, + + /// + /// The start of the current day or an offset in days. + /// + Day, + + /// + /// The start of the current week or an offset in weeks. + /// + Week, + + /// + /// The start of the current month or an offset in months. + /// + Month, + + /// + /// The start of the current year or an offset in years. + /// + Year + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ResultCollection.cs b/Technosoftware/DaAeHdaClient/Hda/ResultCollection.cs new file mode 100644 index 0000000..7cdd1ad --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ResultCollection.cs @@ -0,0 +1,299 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of results passed to write or returned from an insert, replace or delete operation. + /// + [Serializable] + public class TsCHdaResultCollection : OpcItem, ICollection, ICloneable, IList + { + #region Fields + private ArrayList results_ = new ArrayList(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaResultCollection() { } + + /// + /// Initializes object with the specified ItemIdentifier object. + /// + public TsCHdaResultCollection(OpcItem item) : base(item) { } + + /// + /// Initializes object with the specified ResultCollection object. + /// + public TsCHdaResultCollection(TsCHdaResultCollection item) + : base(item) + { + results_ = new ArrayList(item.results_.Count); + + foreach (TsCHdaResult value in item.results_) + { + results_.Add(value.Clone()); + } + } + #endregion + + #region Properties + /// + /// Accessor for elements in the collection. + /// + public TsCHdaResult this[int index] + { + get => (TsCHdaResult)results_[index]; + set => results_[index] = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public override object Clone() + { + var collection = (TsCHdaResultCollection)base.Clone(); + + collection.results_ = new ArrayList(results_.Count); + + foreach (TsCHdaResultCollection value in results_) + { + collection.results_.Add(value.Clone()); + } + + return collection; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => results_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + results_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaResult[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return results_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => results_[index]; + + set + { + if (!(value is TsCHdaResult)) + { + throw new ArgumentException("May only add Result objects into the collection."); + } + + results_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + results_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is TsCHdaResult)) + { + throw new ArgumentException("May only add Result objects into the collection."); + } + + results_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + results_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return results_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + results_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return results_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is TsCHdaResult)) + { + throw new ArgumentException("May only add Result objects into the collection."); + } + + return results_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaResult value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaResult value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaResult value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaResult value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaResult value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Server.cs b/Technosoftware/DaAeHdaClient/Hda/Server.cs new file mode 100644 index 0000000..fa68cff --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Server.cs @@ -0,0 +1,1072 @@ +#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; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// An in-process object used to access OPC Data Access servers. + /// + [Serializable] + public class TsCHdaServer : OpcServer + { + #region Class Names + + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Trends = "Trends"; + } + #endregion + + #region Fields + private Hashtable items_ = new Hashtable(); + private TsCHdaAttributeCollection attributes_ = new TsCHdaAttributeCollection(); + private TsCHdaAggregateCollection aggregates_ = new TsCHdaAggregateCollection(); + private TsCHdaTrendCollection trends_ = new TsCHdaTrendCollection(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with a factory and a default OpcUrl. + /// + /// The TsOpcFactory used to connect to remote servers. + /// The network address of a remote server. + public TsCHdaServer(OpcFactory factory, OpcUrl url) + : + base(factory, url) + { + } + + /// + /// Construct a server by de-serializing its OpcUrl from the stream. + /// + protected TsCHdaServer(SerializationInfo info, StreamingContext context) + : base(info, context) + { + var trends = (TsCHdaTrend[])info.GetValue(Names.Trends, typeof(TsCHdaTrend[])); + + if (trends != null) + { + Array.ForEach(trends, trend => + { + trend.SetServer(this); + trends_.Add(trend); + }); + } + } + #endregion + + #region Properties + /// + /// Returns a collection of item attributes supported by the server. + /// + public TsCHdaAttributeCollection Attributes => attributes_; + + /// + /// Returns a collection of aggregates supported by the server. + /// + public TsCHdaAggregateCollection Aggregates => aggregates_; + + /// + /// Returns a collection of items with server handles assigned to them. + /// + public OpcItemCollection Items => new OpcItemCollection(items_.Values); + + /// + /// Returns a collection of trends created for the server. + /// + public TsCHdaTrendCollection Trends => trends_; + #endregion + + #region Public Methods + /// + /// Connects to the server with the specified OpcUrl and credentials. + /// + public override void Connect(OpcUrl url, OpcConnectData connectData) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + // connect to server. + base.Connect(url, connectData); + + // fetch supported attributes. + GetAttributes(); + + // fetch supported aggregates. + GetAggregates(); + + // create items for trends. + foreach (TsCHdaTrend trend in trends_) + { + var itemIDs = new ArrayList(); + + foreach (TsCHdaItem item in trend.Items) + { + itemIDs.Add(new OpcItem(item)); + } + + // save server handles for each item. + var results = CreateItems((OpcItem[])itemIDs.ToArray(typeof(OpcItem))); + + if (results != null) + { + for (var ii = 0; ii < results.Length; ii++) + { + trend.Items[ii].ServerHandle = null; + + if (results[ii].Result.Succeeded()) + { + trend.Items[ii].ServerHandle = results[ii].ServerHandle; + } + } + } + } + } + + /// + /// Disconnects from the server and releases all network resources. + /// + public override void Disconnect() + { + if (Server == null) throw new NotConnectedException(); + + // dispose of all items first. + if (items_.Count > 0) + { + + try + { + var items = new ArrayList(items_.Count); + items.AddRange(items_); + + ((ITsCHdaServer)Server).ReleaseItems((OpcItem[])items.ToArray(typeof(OpcItem))); + } + catch + { + // ignore errors. + } + + items_.Clear(); + } + + // invalidate server handles for trends. + foreach (TsCHdaTrend trend in trends_) + { + foreach (TsCHdaItem item in trend.Items) + { + item.ServerHandle = null; + } + } + + // disconnect from server. + base.Disconnect(); + } + #endregion + + #region GetStatus + /// + /// Returns the current server status. + /// + /// The current server status. + public OpcServerStatus GetServerStatus() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var status = ((ITsCHdaServer)Server).GetServerStatus(); + + + if (status != null) + { + if (status.StatusInfo == null) + { + status.StatusInfo = GetString($"serverState.{status.ServerState}"); + } + } + else + { + throw new NotConnectedException(); + } + + return status; + } + #endregion + + #region GetAttributes + /// + /// Returns the item attributes supported by the server. + /// + /// The a set of item attributes and their descriptions. + public TsCHdaAttribute[] GetAttributes() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + // clear existing cached list. + attributes_.Clear(); + + var attributes = ((ITsCHdaServer)Server).GetAttributes(); + + // save a locale copy. + if (attributes != null) + { + attributes_.Init(attributes); + } + + return attributes; + } + #endregion + + #region GetAggregates + /// + /// Returns the aggregates supported by the server. + /// + /// The a set of aggregates and their descriptions. + public TsCHdaAggregate[] GetAggregates() + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + // discard existing cached list. + aggregates_.Clear(); + + var aggregates = ((ITsCHdaServer)Server).GetAggregates(); + + // save a locale copy. + if (aggregates != null) + { + aggregates_.Init(aggregates); + } + + return aggregates; + } + #endregion + + #region CreateBrowser + /// + /// Creates a object used to browse the server address space. + /// + /// The set of attribute filters to use when browsing. + /// A result code for each individual filter. + /// A browser object that must be released by calling Dispose(). + public ITsCHdaBrowser CreateBrowser(TsCHdaBrowseFilter[] filters, out OpcResult[] results) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).CreateBrowser(filters, out results); + } + #endregion + + #region CreateItems + /// + /// Creates a set of items. + /// + /// The identifiers for the items to create. + /// The results for each item containing the server handle and result code. + public OpcItemResult[] CreateItems(OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + var results = ((ITsCHdaServer)Server).CreateItems(items); + + // save items for future reference. + if (results != null) + { + foreach (var result in results) + { + if (result.Result.Succeeded()) + { + items_.Add(result.ServerHandle, new OpcItem(result)); + } + } + } + + return results; + } + #endregion + + #region ReleaseItems + /// + /// Releases a set of previously created items. + /// + /// The server handles for the items to release. + /// The results for each item containing the result code. + public OpcItemResult[] ReleaseItems(OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).ReleaseItems(items); + + // remove items from local cache. + if (results != null) + { + foreach (var result in results) + { + if (result.Result.Succeeded()) + { + items_.Remove(result.ServerHandle); + } + } + } + + return results; + } + #endregion + + #region ValidateItems + /// + /// Validates a set of items. + /// + /// The identifiers for the items to validate. + /// The results for each item containing the result code. + public OpcItemResult[] ValidateItems(OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ValidateItems(items); + } + #endregion + + #region ReadRaw + /// + /// Reads raw (unprocessed) data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// Whether the bounding item values should be returned. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + internal TsCHdaItemValueCollection[] ReadRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + bool includeBounds, + OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ReadRaw(startTime, endTime, maxValues, includeBounds, items); + } + + /// + /// Sends an asynchronous request to read raw data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// Whether the bounding item values should be returned. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] ReadRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + bool includeBounds, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ReadRaw(startTime, endTime, maxValues, includeBounds, items, requestHandle, callback, out request); + } + + /// + /// Requests that the server periodically send notifications when new data becomes available for a set of items. + /// + /// The beginning of the history period to read. + /// The frequency, in seconds, that the server should check for new data. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] AdviseRaw( + TsCHdaTime startTime, + decimal updateInterval, + OpcItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).AdviseRaw(startTime, updateInterval, items, requestHandle, callback, out request); + } + + /// + /// Begins the playback raw data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The frequency, in seconds, that the server send data. + /// The duration, in seconds, of the timespan returned with each update. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] PlaybackRaw( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + decimal updateInterval, + decimal playbackDuration, + OpcItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).PlaybackRaw(startTime, endTime, maxValues, updateInterval, playbackDuration, items, requestHandle, callback, out request); + } + #endregion + + #region ReadProcessed + /// + /// Reads processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + internal TsCHdaItemValueCollection[] ReadProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + TsCHdaItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ReadProcessed(startTime, endTime, resampleInterval, items); + } + + /// + /// Sends an asynchronous request to read processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] ReadProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).ReadProcessed( + startTime, + endTime, + resampleInterval, + items, + requestHandle, + callback, + out request); + + return results; + } + + /// + /// Requests that the server periodically send notifications when new data becomes available for a set of items. + /// + /// The beginning of the history period to read. + /// The interval between returned values. + /// The number of resample intervals that the server should return in each callback. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] AdviseProcessed( + TsCHdaTime startTime, + decimal resampleInterval, + int numberOfIntervals, + TsCHdaItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).AdviseProcessed( + startTime, + resampleInterval, + numberOfIntervals, + items, + requestHandle, + callback, + out request); + + return results; + } + + /// + /// Begins the playback of processed data from the historian database for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The interval between returned values. + /// The number of resample intervals that the server should return in each callback. + /// The frequency, in seconds, that the server send data. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] PlaybackProcessed( + TsCHdaTime startTime, + TsCHdaTime endTime, + decimal resampleInterval, + int numberOfIntervals, + decimal updateInterval, + TsCHdaItem[] items, + object requestHandle, + TsCHdaDataUpdateEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).PlaybackProcessed( + startTime, + endTime, + resampleInterval, + numberOfIntervals, + updateInterval, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region ReadAtTime + /// + /// Reads data from the historian database for a set of items at specific times. + /// + /// The set of timestamps to use when reading items values. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + internal TsCHdaItemValueCollection[] ReadAtTime(DateTime[] timestamps, OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ReadAtTime(timestamps, items); + } + + /// + /// Sends an asynchronous request to read item values at specific times. + /// + /// The set of timestamps to use when reading items values. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] ReadAtTime( + DateTime[] timestamps, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).ReadAtTime( + timestamps, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region ReadModified + /// + /// Reads item values that have been deleted or replaced. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The set of items to read (must include the item name). + /// A set of values, qualities and timestamps within the requested time range for each item. + internal TsCHdaModifiedValueCollection[] ReadModified( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ReadModified(startTime, endTime, maxValues, items); + } + + /// + /// Sends an asynchronous request to read item values that have been deleted or replaced. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The number of values to be read for each item. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] ReadModified( + TsCHdaTime startTime, + TsCHdaTime endTime, + int maxValues, + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).ReadModified( + startTime, + endTime, + maxValues, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region ReadAttributes + /// + /// Reads the current or historical values for the attributes of an item. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The item to read (must include the item name). + /// The attributes to read. + /// A set of attribute values for each requested attribute. + internal TsCHdaItemAttributeCollection ReadAttributes( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem item, + int[] attributeIDs) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ReadAttributes(startTime, endTime, item, attributeIDs); + } + + /// + /// Sends an asynchronous request to read the attributes of an item. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The item to read (must include the item name). + /// The attributes to read. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the attribute ids. + internal TsCHdaResultCollection ReadAttributes( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem item, + int[] attributeIDs, + object requestHandle, + TsCHdaReadAttributesCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + var results = ((ITsCHdaServer)Server).ReadAttributes( + startTime, + endTime, + item, + attributeIDs, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region ReadAnnotations + /// + /// Reads any annotations for an item within the a time interval. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The set of items to read (must include the item name). + /// A set of annotations within the requested time range for each item. + internal TsCHdaAnnotationValueCollection[] ReadAnnotations( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).ReadAnnotations(startTime, endTime, items); + } + + /// + /// Sends an asynchronous request to read the annotations for a set of items. + /// + /// The beginning of the history period to read. + /// The end of the history period to be read. + /// The set of items to read (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] ReadAnnotations( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items, + object requestHandle, + TsCHdaReadAnnotationsCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).ReadAnnotations( + startTime, + endTime, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region InsertAnnotations + /// + /// Inserts annotations for one or more items. + /// + /// A list of annotations to add for each item (must include the item name). + /// The results of the insert operation for each annotation set. + public TsCHdaResultCollection[] InsertAnnotations(TsCHdaAnnotationValueCollection[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).InsertAnnotations(items); + } + /// + /// Sends an asynchronous request to inserts annotations for one or more items. + /// + /// A list of annotations to add for each item (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] InsertAnnotations( + TsCHdaAnnotationValueCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).InsertAnnotations( + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region Insert + /// + /// Inserts the values into the history database for one or more items. + /// + /// The set of values to insert. + /// Whether existing values should be replaced. + /// + public TsCHdaResultCollection[] Insert(TsCHdaItemValueCollection[] items, bool replace) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).Insert(items, replace); + } + + /// + /// Sends an asynchronous request to inserts values for one or more items. + /// + /// The set of values to insert. + /// Whether existing values should be replaced. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Insert( + TsCHdaItemValueCollection[] items, + bool replace, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).Insert( + items, + replace, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region Replace + /// + /// Replace the values into the history database for one or more items. + /// + /// The set of values to replace. + /// + public TsCHdaResultCollection[] Replace(TsCHdaItemValueCollection[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).Replace(items); + } + + + /// + /// Sends an asynchronous request to replace values for one or more items. + /// + /// The set of values to replace. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + public OpcItemResult[] Replace( + TsCHdaItemValueCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).Replace( + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region Delete + /// + /// Deletes the values with the specified time domain for one or more items. + /// + /// The beginning of the history period to delete. + /// The end of the history period to be delete. + /// The set of items to delete (must include the item name). + /// The results of the delete operation for each item. + internal OpcItemResult[] Delete( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).Delete(startTime, endTime, items); + } + + /// + /// Sends an asynchronous request to delete values for one or more items. + /// + /// The beginning of the history period to delete. + /// The end of the history period to be delete. + /// The set of items to delete (must include the item name). + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] Delete( + TsCHdaTime startTime, + TsCHdaTime endTime, + OpcItem[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).Delete( + startTime, + endTime, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region DeleteAtTime + /// + /// Deletes the values at the specified times for one or more items. + /// + /// The set of timestamps to delete for one or more items. + /// The results of the operation for each timestamp. + internal TsCHdaResultCollection[] DeleteAtTime(TsCHdaItemTimeCollection[] items) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + return ((ITsCHdaServer)Server).DeleteAtTime(items); + } + + /// + /// Sends an asynchronous request to delete values for one or more items at a specified times. + /// + /// The set of timestamps to delete for one or more items. + /// An identifier for the request assigned by the caller. + /// A delegate used to receive notifications when the request completes. + /// An object that contains the state of the request (used to cancel the request). + /// A set of results containing any errors encountered when the server validated the items. + internal OpcItemResult[] DeleteAtTime( + TsCHdaItemTimeCollection[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + + var results = ((ITsCHdaServer)Server).DeleteAtTime( + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region CancelRequest + /// + /// Cancels an asynchronous request. + /// + /// The state object for the request to cancel. + public void CancelRequest(IOpcRequest request) + { + LicenseHandler.ValidateFeatures(LicenseHandler.ProductFeature.HistoricalAccess); + if (Server == null) throw new NotConnectedException(); + ((ITsCHdaServer)Server).CancelRequest(request); + } + + /// + /// Cancels an asynchronous request. + /// + /// The state object for the request to cancel. + /// A delegate used to receive notifications when the request completes. + public void CancelRequest(IOpcRequest request, TsCHdaCancelCompleteEventHandler callback) + { + if (Server == null) throw new NotConnectedException(); + ((ITsCHdaServer)Server).CancelRequest(request, callback); + } + #endregion + + #region ISerializable Members + /// + /// Serializes a server into a stream. + /// + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + + TsCHdaTrend[] trends = null; + + if (trends_.Count > 0) + { + trends = new TsCHdaTrend[trends_.Count]; + + for (var ii = 0; ii < trends.Length; ii++) + { + trends[ii] = trends_[ii]; + } + } + + info.AddValue(Names.Trends, trends); + } + #endregion + + #region ICloneable Members + /// + /// Returns an unconnected copy of the server with the same OpcUrl. + /// + public override object Clone() + { + // clone the base object. + var clone = (TsCHdaServer)base.Clone(); + + // return clone. + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/ServerState.cs b/Technosoftware/DaAeHdaClient/Hda/ServerState.cs new file mode 100644 index 0000000..66b86e8 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/ServerState.cs @@ -0,0 +1,47 @@ +#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 +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// The set of possible server states. + /// + public enum TsCHdaServerState + { + /// + /// The historian is running. + /// + Up = 1, + + /// + /// The historian is not running. + /// + Down = 2, + + /// + /// The status of the historian is indeterminate. + /// + Indeterminate = 3 + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Time.cs b/Technosoftware/DaAeHdaClient/Hda/Time.cs new file mode 100644 index 0000000..138c544 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Time.cs @@ -0,0 +1,303 @@ +#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.Text; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A time specified as either an absolute or relative value. + /// + [Serializable] + public class TsCHdaTime + { + #region Fields + + private DateTime absoluteTime_ = DateTime.MinValue; + private TsCHdaRelativeTime baseTime_ = TsCHdaRelativeTime.Now; + private TsCHdaTimeOffsetCollection offsets_ = new TsCHdaTimeOffsetCollection(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with its default values. + /// + public TsCHdaTime() { } + + /// + /// Initializes the object with an absolute time. + /// + /// The absolute time. + public TsCHdaTime(DateTime time) + { + AbsoluteTime = time; + } + + /// + /// Initializes the object with a relative time. + /// + /// The relative time. + public TsCHdaTime(string time) + { + var value = Parse(time); + + absoluteTime_ = DateTime.MinValue; + baseTime_ = value.baseTime_; + offsets_ = value.offsets_; + } + #endregion + + #region Properties + /// + /// Whether the time is a relative or absolute time. + /// + public bool IsRelative + { + get => (absoluteTime_ == DateTime.MinValue); + set => absoluteTime_ = DateTime.MinValue; + } + + /// + /// The time as absolute value. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime AbsoluteTime + { + get => absoluteTime_; + set => absoluteTime_ = value; + } + + /// + /// The base for a relative time value. + /// + public TsCHdaRelativeTime BaseTime + { + get => baseTime_; + set => baseTime_ = value; + } + + /// + /// The set of offsets to be applied to the base of a relative time. + /// + public TsCHdaTimeOffsetCollection Offsets => offsets_; + #endregion + + #region Public Methods + /// + /// Converts a relative time to an absolute time by using the system clock. + /// + public DateTime ResolveTime() + { + // nothing special to do for absolute times. + if (!IsRelative) + { + return absoluteTime_; + } + + // get local time from the system. + var time = DateTime.UtcNow; + + var years = time.Year; + var months = time.Month; + var days = time.Day; + var hours = time.Hour; + var minutes = time.Minute; + var seconds = time.Second; + var milliseconds = time.Millisecond; + + // move to the beginning of the period indicated by the base time. + switch (BaseTime) + { + case TsCHdaRelativeTime.Year: + { + months = 0; + days = 0; + hours = 0; + minutes = 0; + seconds = 0; + milliseconds = 0; + break; + } + + case TsCHdaRelativeTime.Month: + { + days = 0; + hours = 0; + minutes = 0; + seconds = 0; + milliseconds = 0; + break; + } + + case TsCHdaRelativeTime.Week: + case TsCHdaRelativeTime.Day: + { + hours = 0; + minutes = 0; + seconds = 0; + milliseconds = 0; + break; + } + + case TsCHdaRelativeTime.Hour: + { + minutes = 0; + seconds = 0; + milliseconds = 0; + break; + } + + case TsCHdaRelativeTime.Minute: + { + seconds = 0; + milliseconds = 0; + break; + } + + case TsCHdaRelativeTime.Second: + { + milliseconds = 0; + break; + } + } + + // construct base time. + time = new DateTime(years, months, days, hours, minutes, seconds, milliseconds); + + // adjust to beginning of week. + if (BaseTime == TsCHdaRelativeTime.Week && time.DayOfWeek != DayOfWeek.Sunday) + { + time = time.AddDays(-((int)time.DayOfWeek)); + } + + // add offsets. + foreach (TsCHdaTimeOffset offset in Offsets) + { + switch (offset.Type) + { + case TsCHdaRelativeTime.Year: { time = time.AddYears(offset.Value); break; } + case TsCHdaRelativeTime.Month: { time = time.AddMonths(offset.Value); break; } + case TsCHdaRelativeTime.Week: { time = time.AddDays(offset.Value * 7); break; } + case TsCHdaRelativeTime.Day: { time = time.AddDays(offset.Value); break; } + case TsCHdaRelativeTime.Hour: { time = time.AddHours(offset.Value); break; } + case TsCHdaRelativeTime.Minute: { time = time.AddMinutes(offset.Value); break; } + case TsCHdaRelativeTime.Second: { time = time.AddSeconds(offset.Value); break; } + } + } + + // return resolved time. + return time; + } + + /// + /// Returns a String that represents the current Object. + /// + /// A String that represents the current Object. + public override string ToString() + { + if (!IsRelative) + { + return OpcConvert.ToString(absoluteTime_); + } + + var buffer = new StringBuilder(256); + + buffer.Append(BaseTypeToString(BaseTime)); + buffer.Append(Offsets); + + return buffer.ToString(); + } + + /// + /// Parses a string representation of a time. + /// + /// The string representation to parse. + /// A Time object initialized with the string. + public static TsCHdaTime Parse(string buffer) + { + // remove trailing and leading white spaces. + buffer = buffer.Trim(); + + var time = new TsCHdaTime(); + + // determine if string is a relative time. + var isRelative = false; + + foreach (TsCHdaRelativeTime baseTime in Enum.GetValues(typeof(TsCHdaRelativeTime))) + { + var token = BaseTypeToString(baseTime); + + if (buffer.StartsWith(token)) + { + buffer = buffer.Substring(token.Length).Trim(); + time.BaseTime = baseTime; + isRelative = true; + break; + } + } + + // parse an absolute time string. + if (!isRelative) + { + time.AbsoluteTime = Convert.ToDateTime(buffer).ToUniversalTime(); + return time; + } + + // parse the offset portion of the relative time. + if (buffer.Length > 0) + { + time.Offsets.Parse(buffer); + } + + return time; + } + #endregion + + #region Public Methods + /// + /// Converts a base time to a string token. + /// + /// The base time value to convert. + /// The string token representing the base time. + private static string BaseTypeToString(TsCHdaRelativeTime baseTime) + { + switch (baseTime) + { + case TsCHdaRelativeTime.Now: { return "NOW"; } + case TsCHdaRelativeTime.Second: { return "SECOND"; } + case TsCHdaRelativeTime.Minute: { return "MINUTE"; } + case TsCHdaRelativeTime.Hour: { return "HOUR"; } + case TsCHdaRelativeTime.Day: { return "DAY"; } + case TsCHdaRelativeTime.Week: { return "WEEK"; } + case TsCHdaRelativeTime.Month: { return "MONTH"; } + case TsCHdaRelativeTime.Year: { return "YEAR"; } + } + + throw new ArgumentOutOfRangeException(nameof(baseTime), baseTime.ToString(), @"Invalid value for relative base time."); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/TimeOffset.cs b/Technosoftware/DaAeHdaClient/Hda/TimeOffset.cs new file mode 100644 index 0000000..c25b615 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/TimeOffset.cs @@ -0,0 +1,70 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// An offset component of a relative time. + /// + [Serializable] + public struct TsCHdaTimeOffset + { + #region Properties + /// + /// A signed value indicated the magnitude of the time offset. + /// + public int Value { get; set; } + + /// + /// The time interval to use when applying the offset. + /// + public TsCHdaRelativeTime Type { get; set; } + #endregion + + #region Internal Methods + /// + /// Converts a offset type to a string token. + /// + /// The offset type value to convert. + /// The string token representing the offset type. + internal static string OffsetTypeToString(TsCHdaRelativeTime offsetType) + { + switch (offsetType) + { + case TsCHdaRelativeTime.Second: { return "S"; } + case TsCHdaRelativeTime.Minute: { return "M"; } + case TsCHdaRelativeTime.Hour: { return "H"; } + case TsCHdaRelativeTime.Day: { return "D"; } + case TsCHdaRelativeTime.Week: { return "W"; } + case TsCHdaRelativeTime.Month: { return "MO"; } + case TsCHdaRelativeTime.Year: { return "Y"; } + } + + throw new ArgumentOutOfRangeException(nameof(offsetType), offsetType.ToString(), @"Invalid value for relative time offset type."); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/TimeOffsetCollection.cs b/Technosoftware/DaAeHdaClient/Hda/TimeOffsetCollection.cs new file mode 100644 index 0000000..a70361c --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/TimeOffsetCollection.cs @@ -0,0 +1,265 @@ +#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.Text; +using System.Collections; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of time offsets used in a relative time. + /// + [Serializable] + public class TsCHdaTimeOffsetCollection : ArrayList + { + #region Properties + /// + /// Accessor for elements in the time offset collection. + /// + public new TsCHdaTimeOffset this[int index] + { + get => this[index]; + set => this[index] = value; + } + #endregion + + #region Public Methods + /// + /// Adds a new offset to the collection. + /// + /// The offset value. + /// The offset type. + public int Add(int value, TsCHdaRelativeTime type) + { + var offset = new TsCHdaTimeOffset { Value = value, Type = type }; + + return base.Add(offset); + } + + /// + /// Returns a String that represents the current Object. + /// + /// A String that represents the current Object. + public override string ToString() + { + var buffer = new StringBuilder(256); + + foreach (TsCHdaTimeOffset offset in (ICollection)this) + { + if (offset.Value >= 0) + { + buffer.Append("+"); + } + + buffer.AppendFormat("{0}", offset.Value); + buffer.Append(TsCHdaTimeOffset.OffsetTypeToString(offset.Type)); + } + + return buffer.ToString(); + } + + /// + /// Initializes the collection from a set of offsets contained in a string. + /// + /// A string containing the time offset fields. + public void Parse(string buffer) + { + // clear existing offsets. + Clear(); + + // parse the offsets. + var positive = true; + var magnitude = 0; + var units = ""; + var state = 0; + + // state = 0 - looking for start of next offset field. + // state = 1 - looking for beginning of offset value. + // state = 2 - reading offset value. + // state = 3 - reading offset type. + + for (var ii = 0; ii < buffer.Length; ii++) + { + // check for sign part of the offset field. + if (buffer[ii] == '+' || buffer[ii] == '-') + { + if (state == 3) + { + Add(CreateOffset(positive, magnitude, units)); + + magnitude = 0; + units = ""; + state = 0; + } + + if (state != 0) + { + throw new FormatException("Unexpected token encountered while parsing relative time string."); + } + + positive = buffer[ii] == '+'; + state = 1; + } + + // check for integer part of the offset field. + else if (char.IsDigit(buffer, ii)) + { + if (state == 3) + { + Add(CreateOffset(positive, magnitude, units)); + + magnitude = 0; + units = ""; + state = 0; + } + + if (state != 0 && state != 1 && state != 2) + { + throw new FormatException("Unexpected token encountered while parsing relative time string."); + } + + magnitude *= 10; + magnitude += Convert.ToInt32(buffer[ii] - '0'); + + state = 2; + } + + // check for units part of the offset field. + else if (!char.IsWhiteSpace(buffer, ii)) + { + if (state != 2 && state != 3) + { + throw new FormatException("Unexpected token encountered while parsing relative time string."); + } + + units += buffer[ii]; + state = 3; + } + } + + // process final field. + if (state == 3) + { + Add(CreateOffset(positive, magnitude, units)); + state = 0; + } + + // check final state. + if (state != 0) + { + throw new FormatException("Unexpected end of string encountered while parsing relative time string."); + } + } + #endregion + + #region ICollection Members + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaTimeOffset[] array, int index) + { + CopyTo((Array)array, index); + } + #endregion + + #region IList Members + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaTimeOffset value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaTimeOffset value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaTimeOffset value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaTimeOffset value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaTimeOffset value) + { + return Add((object)value); + } + #endregion + + #region Private Methods + /// + /// Creates a new offset object from the components extracted from a string. + /// + private static TsCHdaTimeOffset CreateOffset(bool positive, int magnitude, string units) + { + foreach (TsCHdaRelativeTime offsetType in Enum.GetValues(typeof(TsCHdaRelativeTime))) + { + if (offsetType == TsCHdaRelativeTime.Now) + { + continue; + } + + if (units == TsCHdaTimeOffset.OffsetTypeToString(offsetType)) + { + var offset = new TsCHdaTimeOffset { Value = (positive) ? magnitude : -magnitude, Type = offsetType }; + + return offset; + } + } + + throw new ArgumentOutOfRangeException(nameof(units), units, @"String is not a valid offset time type."); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/Trend.cs b/Technosoftware/DaAeHdaClient/Hda/Trend.cs new file mode 100644 index 0000000..f1d6aa7 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/Trend.cs @@ -0,0 +1,1042 @@ +#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.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// Manages a set of items and a set of read, update, subscribe or playback request parameters. + /// + [Serializable] + public class TsCHdaTrend : ISerializable, ICloneable + { + #region Class Names + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Name = "Name"; + internal const string AggregateId = "AggregateID"; + internal const string StartTime = "StartTime"; + internal const string EndTime = "EndTime"; + internal const string MaxValues = "MaxValues"; + internal const string IncludeBounds = "IncludeBounds"; + internal const string ResampleInterval = "ResampleInterval"; + internal const string UpdateInterval = "UpdateInterval"; + internal const string PlaybackInterval = "PlaybackInterval"; + internal const string PlaybackDuration = "PlaybackDuration"; + internal const string Timestamps = "Timestamps"; + internal const string Items = "Items"; + } + #endregion + + #region Fields + private static int count_; + + private TsCHdaServer hdaServer_; + private int aggregate_ = TsCHdaAggregateID.NoAggregate; + private decimal resampleInterval_; + private TsCHdaItemTimeCollection timeStamps_ = new TsCHdaItemTimeCollection(); + private TsCHdaItemCollection items_ = new TsCHdaItemCollection(); + private decimal updateInterval_; + private decimal playbackInterval_; + private decimal playbackDuration_; + + private IOpcRequest subscription_; + private IOpcRequest playback_; + #endregion + + #region Constructors, Destructor, Initialization + + /// + /// Initializes the object with the specified server. + /// + public TsCHdaTrend(TsCHdaServer server) + { + // save a reference to a server. + hdaServer_ = server ?? throw new ArgumentNullException(nameof(server)); + + // create a default name. + do + { + Name = $"Trend{++count_,2:00}"; + } + while (hdaServer_.Trends[Name] != null); + } + + /// + /// Construct a server by de-serializing its OpcUrl from the stream. + /// + protected TsCHdaTrend(SerializationInfo info, StreamingContext context) + { + // deserialize basic parameters. + Name = (string)info.GetValue(Names.Name, typeof(string)); + aggregate_ = (int)info.GetValue(Names.AggregateId, typeof(int)); + StartTime = (TsCHdaTime)info.GetValue(Names.StartTime, typeof(TsCHdaTime)); + EndTime = (TsCHdaTime)info.GetValue(Names.EndTime, typeof(TsCHdaTime)); + MaxValues = (int)info.GetValue(Names.MaxValues, typeof(int)); + IncludeBounds = (bool)info.GetValue(Names.IncludeBounds, typeof(bool)); + resampleInterval_ = (decimal)info.GetValue(Names.ResampleInterval, typeof(decimal)); + updateInterval_ = (decimal)info.GetValue(Names.UpdateInterval, typeof(decimal)); + playbackInterval_ = (decimal)info.GetValue(Names.PlaybackInterval, typeof(decimal)); + playbackDuration_ = (decimal)info.GetValue(Names.PlaybackDuration, typeof(decimal)); + + // deserialize timestamps. + var timestamps = (DateTime[])info.GetValue(Names.Timestamps, typeof(DateTime[])); + + if (timestamps != null) + { + Array.ForEach(timestamps, timestamp => timeStamps_.Add(timestamp)); + } + + // deserialize items. + var items = (TsCHdaItem[])info.GetValue(Names.Items, typeof(TsCHdaItem[])); + + if (items != null) + { + Array.ForEach(items, item => items_.Add(item)); + } + } + #endregion + + #region Properties + /// + /// The server containing the data in the trend. + /// + public TsCHdaServer Server => hdaServer_; + + /// + /// A name for the trend used to display to the user. + /// + public string Name { get; set; } + + /// + /// The default aggregate to use for the trend. + /// + public int Aggregate + { + get => aggregate_; + set => aggregate_ = value; + } + + /// + /// The start time for the trend. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public TsCHdaTime StartTime { get; set; } + + /// + /// The end time for the trend. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public TsCHdaTime EndTime { get; set; } + + /// + /// The maximum number of data points per item in the trend. + /// + public int MaxValues { get; set; } + + /// + /// Whether the trend includes the bounding values. + /// + public bool IncludeBounds { get; set; } + + /// + /// The re-sampling interval (in seconds) to use for processed reads. + /// + public decimal ResampleInterval + { + get => resampleInterval_; + set => resampleInterval_ = value; + } + + /// + /// The discrete set of timestamps for the trend. + /// + public TsCHdaItemTimeCollection Timestamps + { + get => timeStamps_; + + set => timeStamps_ = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// The interval between updates from the server when subscribing to new data. + /// + /// This specifies a number of seconds for raw data or the number of re-sample intervals for processed data. + public decimal UpdateInterval + { + get => updateInterval_; + set => updateInterval_ = value; + } + + /// + /// Whether the server is currently sending updates for the trend. + /// + public bool SubscriptionActive => subscription_ != null; + + /// + /// The interval between updates from the server when playing back existing data. + /// + /// This specifies a number of seconds for raw data and for processed data. + public decimal PlaybackInterval + { + get => playbackInterval_; + set => playbackInterval_ = value; + } + + /// + /// The amount of data that should be returned with each update when playing back existing data. + /// + /// This specifies a number of seconds for raw data or the number of re-sample intervals for processed data. + public decimal PlaybackDuration + { + get => playbackDuration_; + set => playbackDuration_ = value; + } + + /// + /// Whether the server is currently playing data back for the trend. + /// + public bool PlaybackActive => playback_ != null; + + /// + /// The items + /// + public TsCHdaItemCollection Items => items_; + #endregion + + #region Public Methods + /// + /// Returns the items in a trend as an array. + /// + public TsCHdaItem[] GetItems() + { + var items = new TsCHdaItem[items_.Count]; + + for (var ii = 0; ii < items_.Count; ii++) + { + items[ii] = items_[ii]; + } + + return items; + } + + /// + /// Creates a handle for an item and adds it to the trend. + /// + public TsCHdaItem AddItem(OpcItem itemId) + { + if (itemId == null) throw new ArgumentNullException(nameof(itemId)); + + // assign client handle. + if (itemId.ClientHandle == null) + { + itemId.ClientHandle = Guid.NewGuid().ToString(); + } + + // create server handle. + var results = hdaServer_.CreateItems(new[] { itemId }); + + // check for valid results. + if (results == null || results.Length != 1) + { + throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The browse operation cannot continue"); + } + + // check result code. + if (results[0].Result.Failed()) + { + throw new OpcResultException(results[0].Result, "Could not add item to trend."); + } + + // add new item. + var item = new TsCHdaItem(results[0]); + items_.Add(item); + + // return new item. + return item; + } + + /// + /// Removes an item from the trend. + /// + public void RemoveItem(TsCHdaItem item) + { + if (item == null) throw new ArgumentNullException(nameof(item)); + + for (var ii = 0; ii < items_.Count; ii++) + { + if (item.Equals(items_[ii])) + { + hdaServer_.ReleaseItems(new OpcItem[] { item }); + items_.RemoveAt(ii); + return; + } + } + + throw new ArgumentOutOfRangeException(nameof(item), item.Key, @"Item not found in collection."); + } + + /// + /// Removes all items from the trend. + /// + public void ClearItems() + { + hdaServer_.ReleaseItems(GetItems()); + items_.Clear(); + } + + #region Read + /// + /// Reads the values for a for all items in the trend. + /// + public TsCHdaItemValueCollection[] Read() + { + return Read(GetItems()); + } + + /// + /// Reads the values for a for a set of items. + /// + public TsCHdaItemValueCollection[] Read(TsCHdaItem[] items) + { + // read raw data. + if (Aggregate == TsCHdaAggregateID.NoAggregate) + { + return ReadRaw(items); + } + + // read processed data. + return ReadProcessed(items); + } + + + /// + /// Starts an asynchronous read request for all items in the trend. + /// + public OpcItemResult[] Read( + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + return Read(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous read request for a set of items. + /// + public OpcItemResult[] Read( + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + // read raw data. + if (Aggregate == TsCHdaAggregateID.NoAggregate) + { + return ReadRaw(items, requestHandle, callback, out request); + } + + // read processed data. + + return ReadProcessed(items, requestHandle, callback, out request); + } + #endregion + + #region ReadRaw + /// + /// Reads the raw values for a for all items in the trend. + /// + public TsCHdaItemValueCollection[] ReadRaw() + { + return ReadRaw(GetItems()); + } + + /// + /// Reads the raw values for a for a set of items. + /// + public TsCHdaItemValueCollection[] ReadRaw(TsCHdaItem[] items) + { + var results = hdaServer_.ReadRaw( + StartTime, + EndTime, + MaxValues, + IncludeBounds, + items); + + return results; + } + + /// + /// Starts an asynchronous read raw request for all items in the trend. + /// + public OpcItemResult[] ReadRaw( + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + return Read(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous read raw request for a set of items. + /// + public OpcItemResult[] ReadRaw( + OpcItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + var results = hdaServer_.ReadRaw( + StartTime, + EndTime, + MaxValues, + IncludeBounds, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region ReadProcessed + /// + /// Reads the processed values for a for all items in the trend. + /// + public TsCHdaItemValueCollection[] ReadProcessed() + { + return ReadProcessed(GetItems()); + } + + /// + /// Reads the processed values for a for a set of items. + /// + public TsCHdaItemValueCollection[] ReadProcessed(TsCHdaItem[] items) + { + var localItems = ApplyDefaultAggregate(items); + + var results = hdaServer_.ReadProcessed( + StartTime, + EndTime, + ResampleInterval, + localItems); + + return results; + } + + /// + /// Starts an asynchronous read processed request for all items in the trend. + /// + public OpcItemResult[] ReadProcessed( + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + return ReadProcessed(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous read processed request for a set of items. + /// + public OpcItemResult[] ReadProcessed( + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + var localItems = ApplyDefaultAggregate(items); + + var results = hdaServer_.ReadProcessed( + StartTime, + EndTime, + ResampleInterval, + localItems, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region Subscribe + /// + /// Establishes a subscription for the trend. + /// + public OpcItemResult[] Subscribe( + object subscriptionHandle, + TsCHdaDataUpdateEventHandler callback) + { + OpcItemResult[] results = null; + + // subscribe to raw data. + if (Aggregate == TsCHdaAggregateID.NoAggregate) + { + results = hdaServer_.AdviseRaw( + StartTime, + UpdateInterval, + GetItems(), + subscriptionHandle, + callback, + out subscription_); + } + + // subscribe processed data. + else + { + var localItems = ApplyDefaultAggregate(GetItems()); + + results = hdaServer_.AdviseProcessed( + StartTime, + ResampleInterval, + (int)UpdateInterval, + localItems, + subscriptionHandle, + callback, + out subscription_); + } + + return results; + } + + /// + /// Cancels an existing subscription. + /// + public void SubscribeCancel() + { + if (subscription_ != null) + { + hdaServer_.CancelRequest(subscription_); + subscription_ = null; + } + } + #endregion + + #region Playback + /// + /// Begins playback of data for a trend. + /// + public OpcItemResult[] Playback( + object playbackHandle, + TsCHdaDataUpdateEventHandler callback) + { + OpcItemResult[] results; + + // playback raw data. + if (Aggregate == TsCHdaAggregateID.NoAggregate) + { + results = hdaServer_.PlaybackRaw( + StartTime, + EndTime, + MaxValues, + PlaybackInterval, + PlaybackDuration, + GetItems(), + playbackHandle, + callback, + out playback_); + } + + // playback processed data. + else + { + var localItems = ApplyDefaultAggregate(GetItems()); + + results = hdaServer_.PlaybackProcessed( + StartTime, + EndTime, + ResampleInterval, + (int)PlaybackDuration, + PlaybackInterval, + localItems, + playbackHandle, + callback, + out playback_); + } + + return results; + } + + /// + /// Cancels an existing playback operation. + /// + public void PlaybackCancel() + { + if (playback_ != null) + { + hdaServer_.CancelRequest(playback_); + playback_ = null; + } + } + #endregion + + #region ReadModified + /// + /// Reads the modified values for all items in the trend. + /// + public TsCHdaModifiedValueCollection[] ReadModified() + { + return ReadModified(GetItems()); + } + + /// + /// Reads the modified values for a for a set of items. + /// + public TsCHdaModifiedValueCollection[] ReadModified(TsCHdaItem[] items) + { + var results = hdaServer_.ReadModified( + StartTime, + EndTime, + MaxValues, + items); + + return results; + } + + /// + /// Starts an asynchronous read modified request for all items in the trend. + /// + public OpcItemResult[] ReadModified( + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + return ReadModified(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous read modified request for a set of items. + /// + public OpcItemResult[] ReadModified( + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + var results = hdaServer_.ReadModified( + StartTime, + EndTime, + MaxValues, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region ReadAtTime + /// + /// Reads the values at specific times for a for all items in the trend. + /// + public TsCHdaItemValueCollection[] ReadAtTime() + { + return ReadAtTime(GetItems()); + } + + /// + /// Reads the values at specific times for a for a set of items. + /// + public TsCHdaItemValueCollection[] ReadAtTime(TsCHdaItem[] items) + { + var timestamps = new DateTime[Timestamps.Count]; + + for (var ii = 0; ii < Timestamps.Count; ii++) + { + timestamps[ii] = Timestamps[ii]; + } + + return hdaServer_.ReadAtTime(timestamps, items); + } + + /// + /// Starts an asynchronous read values at specific times request for all items in the trend. + /// + public OpcItemResult[] ReadAtTime( + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + return ReadAtTime(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous read values at specific times request for a set of items. + /// + public OpcItemResult[] ReadAtTime( + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadValuesCompleteEventHandler callback, + out IOpcRequest request) + { + var timestamps = new DateTime[Timestamps.Count]; + + for (var ii = 0; ii < Timestamps.Count; ii++) + { + timestamps[ii] = Timestamps[ii]; + } + + return hdaServer_.ReadAtTime(timestamps, items, requestHandle, callback, out request); + } + #endregion + + #region ReadAttributes + /// + /// Reads the attributes at specific times for a for an item. + /// + public TsCHdaItemAttributeCollection ReadAttributes(OpcItem item, int[] attributeIDs) + { + return hdaServer_.ReadAttributes(StartTime, EndTime, item, attributeIDs); + } + + /// + /// Starts an asynchronous read attributes at specific times request for an item. + /// + public TsCHdaResultCollection ReadAttributes( + OpcItem item, + int[] attributeIDs, + object requestHandle, + TsCHdaReadAttributesCompleteEventHandler callback, + out IOpcRequest request) + { + var results = hdaServer_.ReadAttributes( + StartTime, + EndTime, + item, + attributeIDs, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region ReadAnnotations + /// + /// Reads the annotations for a for all items in the trend. + /// + public TsCHdaAnnotationValueCollection[] ReadAnnotations() + { + return ReadAnnotations(GetItems()); + } + + /// + /// Reads the annotations for a for a set of items. + /// + public TsCHdaAnnotationValueCollection[] ReadAnnotations(TsCHdaItem[] items) + { + var results = hdaServer_.ReadAnnotations( + StartTime, + EndTime, + items); + + return results; + } + + /// + /// Starts an asynchronous read annotations request for all items in the trend. + /// + public OpcItemResult[] ReadAnnotations( + object requestHandle, + TsCHdaReadAnnotationsCompleteEventHandler callback, + out IOpcRequest request) + { + return ReadAnnotations(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous read annotations request for a set of items. + /// + public OpcItemResult[] ReadAnnotations( + TsCHdaItem[] items, + object requestHandle, + TsCHdaReadAnnotationsCompleteEventHandler callback, + out IOpcRequest request) + { + var results = hdaServer_.ReadAnnotations( + StartTime, + EndTime, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region Delete + /// + /// Deletes the raw values for a for all items in the trend. + /// + public OpcItemResult[] Delete() + { + return Delete(GetItems()); + } + + /// + /// Deletes the raw values for a for a set of items. + /// + public OpcItemResult[] Delete(TsCHdaItem[] items) + { + var results = hdaServer_.Delete( + StartTime, + EndTime, + items); + + return results; + } + + /// + /// Starts an asynchronous delete raw request for all items in the trend. + /// + public OpcItemResult[] Delete( + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + return Delete(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous delete raw request for a set of items. + /// + public OpcItemResult[] Delete( + OpcItem[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + var results = hdaServer_.Delete( + StartTime, + EndTime, + items, + requestHandle, + callback, + out request); + + return results; + } + #endregion + + #region DeleteAtTime + /// + /// Deletes the values at specific times for a for all items in the trend. + /// + public TsCHdaResultCollection[] DeleteAtTime() + { + return DeleteAtTime(GetItems()); + } + + /// + /// Deletes the values at specific times for a for a set of items. + /// + public TsCHdaResultCollection[] DeleteAtTime(TsCHdaItem[] items) + { + var times = new TsCHdaItemTimeCollection[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + times[ii] = (TsCHdaItemTimeCollection)Timestamps.Clone(); + + times[ii].ItemName = items[ii].ItemName; + times[ii].ItemPath = items[ii].ItemPath; + times[ii].ClientHandle = items[ii].ClientHandle; + times[ii].ServerHandle = items[ii].ServerHandle; + } + + return hdaServer_.DeleteAtTime(times); + } + + /// + /// Starts an asynchronous delete values at specific times request for all items in the trend. + /// + public OpcItemResult[] DeleteAtTime( + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + return DeleteAtTime(GetItems(), requestHandle, callback, out request); + } + + /// + /// Starts an asynchronous delete values at specific times request for a set of items. + /// + public OpcItemResult[] DeleteAtTime( + TsCHdaItem[] items, + object requestHandle, + TsCHdaUpdateCompleteEventHandler callback, + out IOpcRequest request) + { + var times = new TsCHdaItemTimeCollection[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + times[ii] = (TsCHdaItemTimeCollection)Timestamps.Clone(); + + times[ii].ItemName = items[ii].ItemName; + times[ii].ItemPath = items[ii].ItemPath; + times[ii].ClientHandle = items[ii].ClientHandle; + times[ii].ServerHandle = items[ii].ServerHandle; + } + + return hdaServer_.DeleteAtTime(times, requestHandle, callback, out request); + } + #endregion + #endregion + + #region ISerializable Members + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + // serialize basic parameters. + info.AddValue(Names.Name, Name); + info.AddValue(Names.AggregateId, aggregate_); + info.AddValue(Names.StartTime, StartTime); + info.AddValue(Names.EndTime, EndTime); + info.AddValue(Names.MaxValues, MaxValues); + info.AddValue(Names.IncludeBounds, IncludeBounds); + info.AddValue(Names.ResampleInterval, resampleInterval_); + info.AddValue(Names.UpdateInterval, updateInterval_); + info.AddValue(Names.PlaybackInterval, playbackInterval_); + info.AddValue(Names.PlaybackDuration, playbackDuration_); + + // serialize timestamps. + DateTime[] timestamps = null; + + if (timeStamps_.Count > 0) + { + timestamps = new DateTime[timeStamps_.Count]; + + for (var ii = 0; ii < timestamps.Length; ii++) + { + timestamps[ii] = timeStamps_[ii]; + } + } + + info.AddValue(Names.Timestamps, timestamps); + + // serialize items. + TsCHdaItem[] items = null; + + if (items_.Count > 0) + { + items = new TsCHdaItem[items_.Count]; + + for (var ii = 0; ii < items.Length; ii++) + { + items[ii] = items_[ii]; + } + } + + info.AddValue(Names.Items, items); + } + + /// + /// Used to set the server after the object is deserialized. + /// + internal void SetServer(TsCHdaServer server) + { + hdaServer_ = server; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + // clone simple properties. + var clone = (TsCHdaTrend)MemberwiseClone(); + + // clone items. + clone.items_ = new TsCHdaItemCollection(); + + foreach (TsCHdaItem item in items_) + { + clone.items_.Add(item.Clone()); + } + + // clone timestamps. + clone.timeStamps_ = new TsCHdaItemTimeCollection(); + + foreach (DateTime timestamp in timeStamps_) + { + clone.timeStamps_.Add(timestamp); + } + + // clear dynamic state information. + clone.subscription_ = null; + clone.playback_ = null; + + return clone; + } + #endregion + + #region Private Methods + /// + /// Creates a copy of the items that have a valid aggregate set. + /// + private TsCHdaItem[] ApplyDefaultAggregate(TsCHdaItem[] items) + { + // use interpolative aggregate if none specified for the trend. + var defaultId = Aggregate; + + if (defaultId == TsCHdaAggregateID.NoAggregate) + { + defaultId = TsCHdaAggregateID.Interpolative; + } + + // apply default aggregate to items that have no aggregate specified. + var localItems = new TsCHdaItem[items.Length]; + + for (var ii = 0; ii < items.Length; ii++) + { + localItems[ii] = new TsCHdaItem(items[ii]); + + if (localItems[ii].Aggregate == TsCHdaAggregateID.NoAggregate) + { + localItems[ii].Aggregate = defaultId; + } + } + + // return updated items. + return localItems; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Hda/TrendCollection.cs b/Technosoftware/DaAeHdaClient/Hda/TrendCollection.cs new file mode 100644 index 0000000..1a4deb1 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Hda/TrendCollection.cs @@ -0,0 +1,309 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient.Hda +{ + /// + /// A collection of items. + /// + [Serializable] + public class TsCHdaTrendCollection : ICloneable, IList + { + #region Fields + private ArrayList trends_ = new ArrayList(); + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes object with the default values. + /// + public TsCHdaTrendCollection() { } + + /// + /// Initializes object with the specified TrendValueCollection object. + /// + public TsCHdaTrendCollection(TsCHdaTrendCollection items) + { + if (items != null) + { + foreach (TsCHdaTrend item in items) + { + Add(item); + } + } + } + #endregion + + #region Properties + /// + /// Gets the trend at the specified index. + /// + public TsCHdaTrend this[int index] => (TsCHdaTrend)trends_[index]; + + /// + /// Gets the first trend with the specified name. + /// + public TsCHdaTrend this[string name] + { + get + { + foreach (TsCHdaTrend trend in trends_) + { + if (trend.Name == name) + { + return trend; + } + } + + return null; + } + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + var clone = (TsCHdaTrendCollection)MemberwiseClone(); + + clone.trends_ = new ArrayList(); + + foreach (TsCHdaTrend trend in trends_) + { + clone.trends_.Add(trend.Clone()); + } + + return clone; + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => trends_?.Count ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + trends_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(TsCHdaTrend[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return trends_.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the IList is read-only. + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => trends_[index]; + + set + { + if (!(value is TsCHdaTrend)) + { + throw new ArgumentException("May only add Trend objects into the collection."); + } + + trends_[index] = value; + } + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + trends_.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, object value) + { + if (!(value is TsCHdaTrend)) + { + throw new ArgumentException("May only add Trend objects into the collection."); + } + + trends_.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(object value) + { + trends_.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(object value) + { + return trends_.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public void Clear() + { + trends_.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(object value) + { + return trends_.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(object value) + { + if (!(value is TsCHdaTrend)) + { + throw new ArgumentException("May only add Trend objects into the collection."); + } + + return trends_.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public bool IsFixedSize => false; + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public void Insert(int index, TsCHdaTrend value) + { + Insert(index, (object)value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public void Remove(TsCHdaTrend value) + { + Remove((object)value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public bool Contains(TsCHdaTrend value) + { + return Contains((object)value); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public int IndexOf(TsCHdaTrend value) + { + return IndexOf((object)value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public int Add(TsCHdaTrend value) + { + return Add((object)value); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/Interfaces/IOpcBrowsePosition.cs b/Technosoftware/DaAeHdaClient/Interfaces/IOpcBrowsePosition.cs new file mode 100644 index 0000000..02f3dc3 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Interfaces/IOpcBrowsePosition.cs @@ -0,0 +1,35 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Maintains the state of a browse operation + /// + public interface IOpcBrowsePosition : IDisposable, ICloneable + { + } +} diff --git a/Technosoftware/DaAeHdaClient/Interfaces/IOpcDiscovery.cs b/Technosoftware/DaAeHdaClient/Interfaces/IOpcDiscovery.cs new file mode 100644 index 0000000..2674a96 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Interfaces/IOpcDiscovery.cs @@ -0,0 +1,57 @@ +#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; + +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// This interface is used to discover OPC servers on the network. + /// + public interface IOpcDiscovery : IDisposable + { + /// + /// Returns a list of host names which could contain OPC servers. + /// + /// A array of strings that are valid network host names. + string[] EnumerateHosts(); + + /// + /// Returns a list of servers that support an OPC specification. + /// + /// A unique identifier for an OPC specification. + /// An array of unconnected OPC server obejcts on the local machine. + OpcServer[] GetAvailableServers(OpcSpecification specification); + + /// + /// Returns a list of servers that support an OPC specification on remote machine. + /// + /// A unique identifier for an OPC specification. + /// The network host name of the machine to search for servers. + /// Any necessary user authentication or protocol configuration information. + /// An array of unconnected OPC server objects. + OpcServer[] GetAvailableServers(OpcSpecification specification, string host, OpcConnectData connectData); + } +} diff --git a/Technosoftware/DaAeHdaClient/Interfaces/IOpcFactory.cs b/Technosoftware/DaAeHdaClient/Interfaces/IOpcFactory.cs new file mode 100644 index 0000000..bf9ce7b --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Interfaces/IOpcFactory.cs @@ -0,0 +1,44 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A interface to a factory used to instantiate servers. + /// + public interface IOpcFactory : IDisposable + { + /// + /// Can be used to force OPC DA 2.0 even if OPC DA 3.0 server features are available + /// + bool ForceDa20Usage { get; set; } + + /// + /// Creates a new instance of the server at the specified URL. + /// + IOpcServer CreateInstance(OpcUrl url, OpcConnectData connectData); + } +} diff --git a/Technosoftware/DaAeHdaClient/Interfaces/IOpcRequest.cs b/Technosoftware/DaAeHdaClient/Interfaces/IOpcRequest.cs new file mode 100644 index 0000000..bba6346 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Interfaces/IOpcRequest.cs @@ -0,0 +1,39 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Maintains the state of an asynchronous request. + /// + public interface IOpcRequest + { + /// + /// An unique identifier, assigned by the client, for the request. + /// + object Handle { get; } + } +} diff --git a/Technosoftware/DaAeHdaClient/Interfaces/IOpcResult.cs b/Technosoftware/DaAeHdaClient/Interfaces/IOpcResult.cs new file mode 100644 index 0000000..59c246f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Interfaces/IOpcResult.cs @@ -0,0 +1,44 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A interface used to access result information associated with a single item/value. + /// + public interface IOpcResult + { + /// + /// The error id for the result of an operation on an item. + /// + OpcResult Result { get; set; } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + string DiagnosticInfo { get; set; } + } +} diff --git a/Technosoftware/DaAeHdaClient/Interfaces/IOpcServer.cs b/Technosoftware/DaAeHdaClient/Interfaces/IOpcServer.cs new file mode 100644 index 0000000..cabe564 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Interfaces/IOpcServer.cs @@ -0,0 +1,94 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Defines functionality that is common to all OPC servers. + /// + public interface IOpcServer : IDisposable + { + /// + /// An event to receive server shutdown notifications. This event can be used by the + /// client so that the server can request that the client should disconnect from the + /// server. + /// + /// + /// The OpcServerShutdownEvent event will be called when the server needs to + /// shutdown. The client should release all connections and interfaces for this + /// server.
+ /// A client which is connected to multiple OPCServers (for example Data access and/or + /// other servers such as Alarms and events servers from one or more vendors) should + /// maintain separate shutdown callbacks for each server since any server can shut down + /// independently of the others. + ///
+ event OpcServerShutdownEventHandler ServerShutdownEvent; + + /// + /// The locale used in any error messages or results returned to the client. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + string GetLocale(); + + /// + /// Sets the locale used in any error messages or results returned to the client. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + /// A locale that the server supports and is the best match for the requested locale. + string SetLocale(string locale); + + /// + /// Allows the client to optionally register a client name with the server. This is included primarily for debugging purposes. The recommended behavior is that the client set his Node name and EXE name here. + /// + void SetClientName(string clientName); + + /// + /// Returns the locales supported by the server + /// + /// The first element in the array must be the default locale for the server. + /// An array of locales with the format "[languagecode]-[country/regioncode]". + string[] GetSupportedLocales(); + + /// + /// Returns the localized text for the specified result code. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + /// The result code identifier. + /// A message localized for the best match for the requested locale. + string GetErrorText(string locale, OpcResult resultId); + } + + /// + /// A delegate to receive shutdown notifications from the server. This delegate can + /// be used by the client so that the server can request that the client should disconnect + /// from the server. + /// + /// + /// A text string provided by the server indicating the reason for the shutdown. The + /// server may pass a null or empty string if no reason is provided. + /// + public delegate void OpcServerShutdownEventHandler(string reason); +} diff --git a/Technosoftware/DaAeHdaClient/LicenseHandler.cs b/Technosoftware/DaAeHdaClient/LicenseHandler.cs new file mode 100644 index 0000000..233e121 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/LicenseHandler.cs @@ -0,0 +1,394 @@ +#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.Linq; +using static System.String; +using System.Diagnostics; +using Technosoftware.DaAeHdaClient.Utilities; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Manages the license to enable the different product versions. + /// + public class LicenseHandler + { + #region Nested Enums + /// + /// The possible products. + /// + [Flags] + public enum ProductLicense : uint + { + /// + /// No product selected + /// + None = 0, + + /// + /// OPC DA/AE/HDA Client .NET + /// + Client = 1, + + /// + /// OPC DA/AE Server .NET + /// + Server = 2, + + /// + /// Evaluation + /// + Evaluation = 4, + + /// + /// Expired Evaluation or License + /// + Expired = 8, + } + + /// + /// The possible products. + /// + [Flags] + public enum ProductFeature : uint + { + /// + /// Basic OPC Features enabled + /// + None = 0, + + /// + /// OPC DataAccess enabled + /// + DataAccess = 1, + + /// + /// OPC Alarms and Events enabled + /// + AlarmsConditions = 2, + + /// + /// OPC Historical Dara Access enabled + /// + HistoricalAccess = 4, + + /// + /// All supported OPC DA/AE/HDA Features enabled + /// + AllFeatures = uint.MaxValue, + } + #endregion + + #region Constants + /// + /// License Validation Parameters String for the OPC UA Solution .NET + /// + private const string LicenseParameter = + @""; + #endregion + + #region Internal Fields + internal static bool LicenseTraceDone; + #endregion + + #region Properties + /// + /// Returns whether the product is a licensed product. + /// + /// Returns true if the product is licensed; false if it is used in evaluation mode or license is expired. + public static bool IsLicensed + { + get + { + if ((LicensedProduct == ProductLicense.None) || + ((LicensedProduct & ProductLicense.Expired) == ProductLicense.Expired) || + ((LicensedProduct & ProductLicense.Evaluation) == ProductLicense.Evaluation)) + { + return false; + } + return true; + } + } + + /// + /// Returns whether the product is an evaluation version. + /// + /// Returns true if the product is an evaluation; false if it is a product or license is expired. + public static bool IsEvaluation + { + get + { + if ((LicensedProduct & ProductLicense.Evaluation) == ProductLicense.Evaluation) + { + return true; + } + return false; + } + } + + /// + /// Indicates whether the evaluation period and a restart is required or not. + /// + public static bool IsExpired + { + get + { + if ((LicensedProduct & ProductLicense.Expired) == ProductLicense.Expired) + { + return true; + } + return false; + } + } + + /// + /// Returns the Version of the product. + /// + public static string Version + { + get + { + string versionString; + + try + { + var assembly = (typeof(LicenseHandler).Assembly); + + var versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location); + + var major = versionInfo.FileMajorPart; + var minor = versionInfo.FileMinorPart; + var build = versionInfo.FileBuildPart; + + versionString = $"{major}.{minor}.{build}"; + } + catch (Exception) + { + + versionString = "Unknown"; + } + return versionString; + } + } + + /// + /// Returns the licensed products. + /// + public static ProductLicense LicensedProduct { get; set; } = ProductLicense.Client; + + /// + /// Returns the licensed OPC UA Features. + /// + public static ProductFeature LicensedFeatures { get; set; } = ProductFeature.AllFeatures; + + /// + /// Returns the licensed product name. + /// + public static string Product + { + get + { + var product = "Expired Evaluation or License"; + + if ((LicensedProduct & ProductLicense.Expired) == ProductLicense.Expired) + { + // It's an expired evaluation or license + if (((LicensedProduct & ProductLicense.Client) == ProductLicense.Client) && + ((LicensedProduct & ProductLicense.Server) == ProductLicense.Server)) + { + product = "Expired OPC DA/AE/HDA Bundle .NET license"; + } + else if ((LicensedProduct & ProductLicense.Client) == ProductLicense.Client) + { + product = "Expired OPC DA/AE/HDA Client .NET license"; + } + else if ((LicensedProduct & ProductLicense.Server) == ProductLicense.Server) + { + product = "Expired OPC DA/HDA Server .NET license"; + } + else if ((LicensedProduct & ProductLicense.Evaluation) == ProductLicense.Evaluation) + { + product = "Expired OPC DA/AE/HDA Bundle .NET Evaluation"; + } + return product; + } + + // It's a license or evaluation + if (((LicensedProduct & ProductLicense.Client) == ProductLicense.Client) && + ((LicensedProduct & ProductLicense.Server) == ProductLicense.Server)) + { + product = "OPC DA/AE/HDAUA Bundle .NET"; + } + else if ((LicensedProduct & ProductLicense.Client) == ProductLicense.Client) + { + product = "OPC DA/AE/HDA Client .NET"; + } + else if ((LicensedProduct & ProductLicense.Server) == ProductLicense.Server) + { + product = "OPC DA/AE Server .NET"; + } + else if ((LicensedProduct & ProductLicense.Evaluation) == ProductLicense.Evaluation) + { + product = "OPC DA/AE/HDA Bundle .NET Evaluation"; + } + + return product; + } + } + + /// + /// Returns the product information. + /// + public static string ProductInformation + { + get + { + if (IsLicensed) + { + return Product; + } + if (!Check()) + { + return Product + " EVALUATION EXPIRED !!!"; + } + return Product + " EVALUATION"; + } + } + + internal static bool Checked { get; private set; } + #endregion + + #region Public Methods + /// + /// Validate the license. + /// + /// Serial Number + public static bool Validate(string serialNumber) + { + return CheckLicense(serialNumber); + } + #endregion + + #region Protected Methods + /// + /// Validate the license. + /// + /// Serial Number + protected static bool CheckLicense(string serialNumber) + { + var check = CheckLicenseClient(serialNumber); + CheckProductFeature(serialNumber); + + if (check) + { + return true; + } + return false; + } + + /// + /// Check if the licensed product provided through ValidateLicense qualifies for the given application type and license edition. + /// + /// True if the license qualifies for the requested application and edition or if the evaluation period is still running; otherwise False. + protected static bool CheckLicense() + { + if (Checked && LicensedProduct == ProductLicense.None) + { + return false; + } + + if (Checked && + ((LicensedProduct & ProductLicense.Client) == ProductLicense.Client)) + { + return true; + } + CheckProductFeature(""); + return Check(); + } + + #endregion + + #region Internal Methods + /// + /// Core Feature validation + /// + /// True if valid; false otherwise + /// + public static void ValidateFeatures(ProductFeature requiredProductFeature = ProductFeature.None, bool silent = false) + { + var valid = CheckLicense(); + + if (!LicenseTraceDone) + { + LicenseTraceDone = true; + Utils.Trace("Used Product = {0}, Features = {1}, Version = {2}.", Product, LicensedFeatures, Version); + } + + if (!valid && !IsLicensed && !silent) + { + throw new BadInternalErrorException("Evaluation time expired! You need to restart the application."); + } + if (!valid && !silent) + { + throw new BadInternalErrorException("License required! You can't use this feature."); + } + + if (requiredProductFeature != ProductFeature.None && LicensedFeatures != ProductFeature.AllFeatures) + { + if (((requiredProductFeature & LicensedFeatures) != requiredProductFeature) && !silent) + { + var message = + $"Feature {requiredProductFeature} required but only {LicensedFeatures} licensed! You can't use this feature."; + throw new BadInternalErrorException(message); + } + } + } + #endregion + + #region Private Methods + /// + /// Validate the license. + /// + /// The license key + protected static void CheckProductFeature(string licenseKey) + { + } + + /// + /// Validate the license. + /// + /// The license key + protected static bool CheckLicenseClient(string licenseKey) + { + return true; + } + + internal static bool Check() + { + return true; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcConnectData.cs b/Technosoftware/DaAeHdaClient/OpcConnectData.cs new file mode 100644 index 0000000..168e249 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcConnectData.cs @@ -0,0 +1,191 @@ +#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.Net; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Contains protocol dependent connection and authentication information. + /// + [Serializable] + public class OpcConnectData : ISerializable, ICredentials + { + #region Public Properties + /// + /// The credentials to submit to the proxy server for authentication. + /// + public OpcUserIdentity UserIdentity { get; set; } + + /// + /// The license key used to connect to the server. + /// + public string LicenseKey { get; set; } + + /// + /// Always uses the DA20 interfaces even if DA3.0 is supported. + /// + bool ForceDa20Usage { get; set; } + + /// + /// Use DCOM connect level security (may be needed for backward compatibility). + /// + public bool UseConnectSecurity { get; set; } + #endregion + + #region Public Methods + /// + /// Returns a UserIdentity object that is associated with the specified URI, and authentication type. + /// + public NetworkCredential GetCredential(Uri uri, string authenticationType) + { + if (UserIdentity != null) + { + return new NetworkCredential(UserIdentity.Username, UserIdentity.Password, UserIdentity.Domain); + } + + return null; + } + + /// + /// Returns the web proxy object to use when connecting to the server. + /// + public IWebProxy GetProxy() + { + if (proxy_ != null) + { + return proxy_; + } + else + { + return new WebProxy(); + } + } + + /// + /// Sets the web proxy object. + /// + public void SetProxy(WebProxy proxy) + { + proxy_ = proxy; + } + + /// + /// Initializes an instance with the specified credentials. + /// + public OpcConnectData(OpcUserIdentity userIdentity) + { + UserIdentity = userIdentity; + proxy_ = null; + } + + /// + /// Initializes an instance with the specified credentials and web proxy. + /// + public OpcConnectData(OpcUserIdentity userIdentity, WebProxy proxy) + { + UserIdentity = userIdentity; + proxy_ = proxy; + } + #endregion + + #region ISerializable Members + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string UserName = "UN"; + internal const string Password = "PW"; + internal const string Domain = "DO"; + internal const string ProxyUri = "PU"; + internal const string LicenseKey = "LK"; + } + + /// + /// Construct the object by de-serializing from the stream. + /// + protected OpcConnectData(SerializationInfo info, StreamingContext context) + { + var username = info.GetString(Names.UserName); + var password = info.GetString(Names.Password); + var domain = info.GetString(Names.Domain); + var proxyUri = info.GetString(Names.ProxyUri); + info.GetString(Names.LicenseKey); + + if (domain != null) + { + UserIdentity = new OpcUserIdentity("", ""); + } + else + { + UserIdentity = new OpcUserIdentity(username, password); + } + + if (proxyUri != null) + { + proxy_ = new WebProxy(proxyUri); + } + else + { + proxy_ = null; + } + } + + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (UserIdentity != null) + { + info.AddValue(Names.UserName, UserIdentity.Username); + info.AddValue(Names.Password, UserIdentity.Password); + info.AddValue(Names.Domain, UserIdentity.Domain); + } + else + { + info.AddValue(Names.UserName, null); + info.AddValue(Names.Password, null); + info.AddValue(Names.Domain, null); + } + + if (proxy_ != null) + { + info.AddValue(Names.ProxyUri, proxy_.Address); + } + else + { + info.AddValue(Names.ProxyUri, null); + } + } + #endregion + + #region Private Fields + private WebProxy proxy_; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcConvert.cs b/Technosoftware/DaAeHdaClient/OpcConvert.cs new file mode 100644 index 0000000..5ee9ec9 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcConvert.cs @@ -0,0 +1,544 @@ +#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.Xml; +using System.Collections; +using System.Text; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Defines various functions used to convert types. + /// + public class OpcConvert + { + #region Public Methods + /// + /// Checks whether the array contains any useful data. + /// + public static bool IsValid(Array array) + { + return (array != null && array.Length > 0); + } + + /// + /// Checks whether the array contains any useful data. + /// + public static bool IsEmpty(Array array) + { + return (array == null || array.Length == 0); + } + + /// + /// Checks whether the string contains any useful data. + /// + public static bool IsValid(string target) + { + return !string.IsNullOrEmpty(target); + } + + /// + /// Checks whether the string contains any useful data. + /// + public static bool IsEmpty(string target) + { + return string.IsNullOrEmpty(target); + } + + /// + /// Performs a deep copy of an object if possible. + /// + public static object Clone(object source) + { + if (source == null) return null; + if (source.GetType().IsValueType) return source; + + if (source.GetType().IsArray || source.GetType() == typeof(Array)) + { + var array = (Array)((Array)source).Clone(); + + for (var ii = 0; ii < array.Length; ii++) + { + array.SetValue(Clone(array.GetValue(ii)), ii); + } + + return array; + } + + try { return ((ICloneable)source).Clone(); } + catch { throw new NotSupportedException("Object cannot be cloned."); } + } + + /// + /// Does a deep comparison between two objects. + /// + public static bool Compare(object a, object b) + { + if (a == null || b == null) return (a == null && b == null); + + var type1 = a.GetType(); + var type2 = b.GetType(); + + if (type1 != type2) return false; + + if (type1.IsArray && type2.IsArray) + { + var array1 = (Array)a; + var array2 = (Array)b; + + if (array1.Length != array2.Length) return false; + + for (var ii = 0; ii < array1.Length; ii++) + { + if (!Compare(array1.GetValue(ii), array2.GetValue(ii))) return false; + } + + return true; + } + + return a.Equals(b); + } + + /// + /// Converts an object to the specified type and returns a deep copy. + /// + public static object ChangeType(object source, Type newType) + { + // check for null source object. + if (source == null) + { + if (newType != null && newType.IsValueType) + { + return Activator.CreateInstance(newType); + } + + return null; + } + + // check for null type or 'object' type. + if (newType == null || newType == typeof(object) || newType == source.GetType()) + { + return Clone(source); + } + + var type = source.GetType(); + + // convert between array types. + if (type.IsArray && newType.IsArray) + { + var array = new ArrayList(((Array)source).Length); + + foreach (var element in (Array)source) + { + array.Add(ChangeType(element, newType.GetElementType())); + } + + return array.ToArray(newType.GetElementType() ?? throw new InvalidOperationException()); + } + + // convert scalar value to an array type. + if (!type.IsArray && newType.IsArray) + { + var array = new ArrayList(1) {ChangeType(source, newType.GetElementType())}; + return array.ToArray(newType.GetElementType() ?? throw new InvalidOperationException()); + } + + // convert single element array type to scalar type. + if (type.IsArray && !newType.IsArray && ((Array)source).Length == 1) + { + return Convert.ChangeType(((Array)source).GetValue(0), newType); + } + + // convert array type to string. + if (type.IsArray && newType == typeof(string)) + { + var buffer = new StringBuilder(); + + buffer.Append("{ "); + + var count = 0; + + foreach (var element in (Array)source) + { + buffer.AppendFormat("{0}", ChangeType(element, typeof(string))); + + count++; + + if (count < ((Array)source).Length) + { + buffer.Append(" | "); + } + } + + buffer.Append(" }"); + + return buffer.ToString(); + } + + // convert to enumerated type. + if (newType.IsEnum) + { + if (type == typeof(string)) + { + // check for an integer passed as a string. + if (((string)source).Length > 0 && char.IsDigit((string)source, 0)) + { + return Enum.ToObject(newType, Convert.ToInt32(source)); + } + + // parse a string value. + return Enum.Parse(newType, (string)source); + } + else + { + // convert numerical value to an enum. + return Enum.ToObject(newType, source); + } + } + + // convert to boolean type. + if (newType == typeof(bool)) + { + // check for an integer passed as a string. + if (source is string text) + { + if (text.Length > 0 && (text[0] == '+' || text[0] == '-' || char.IsDigit(text, 0))) + { + return Convert.ToBoolean(Convert.ToInt32(source)); + } + } + + return Convert.ToBoolean(source); + } + + // use default conversion. + return Convert.ChangeType(source, newType); + } + + /// + /// Formats an item or property value as a string. + /// + public static string ToString(object source) + { + // check for null + if (source == null) return ""; + + var type = source.GetType(); + + // check for invalid values in date times. + if (type == typeof(DateTime)) + { + if (((DateTime)source) == DateTime.MinValue) + { + return string.Empty; + } + + var date = (DateTime)source; + + if (date.Millisecond > 0) + { + return date.ToString("yyyy-MM-dd HH:mm:ss.fff"); + } + else + { + return date.ToString("yyyy-MM-dd HH:mm:ss"); + } + } + + // use only the local name for qualified names. + if (type == typeof(XmlQualifiedName)) + { + return ((XmlQualifiedName)source).Name; + } + + // use only the name for system types. + if (type.FullName == "System.RuntimeType") + { + return ((Type)source).Name; + } + + // treat byte arrays as a special case. + if (type == typeof(byte[])) + { + var bytes = (byte[])source; + + var buffer = new StringBuilder(bytes.Length * 3); + + foreach (var character in bytes) + { + buffer.Append(character.ToString("X2")); + buffer.Append(" "); + } + + return buffer.ToString(); + } + + // show the element type and length for arrays. + if (type.IsArray) + { + return $"{type.GetElementType()?.Name}[{((Array)source).Length}]"; + } + + // instances of array are always treated as arrays of objects. + if (type == typeof(Array)) + { + return $"Object[{((Array)source).Length}]"; + } + + // default behavior. + return source.ToString(); + } + + /// + /// Tests if the specified string matches the specified pattern. + /// + public static bool Match(string target, string pattern, bool caseSensitive) + { + // an empty pattern always matches. + if (string.IsNullOrEmpty(pattern)) + { + return true; + } + + // an empty string never matches. + if (string.IsNullOrEmpty(target)) + { + return false; + } + + // check for exact match + if (caseSensitive) + { + if (target == pattern) + { + return true; + } + } + else + { + if (target.ToLower() == pattern.ToLower()) + { + return true; + } + } + + var pIndex = 0; + var tIndex = 0; + + while (tIndex < target.Length && pIndex < pattern.Length) + { + var p = ConvertCase(pattern[pIndex++], caseSensitive); + + if (pIndex > pattern.Length) + { + return (tIndex >= target.Length); // if end of string true + } + + char c; + switch (p) + { + // match zero or more char. + case '*': + { + while (tIndex < target.Length) + { + if (Match(target.Substring(tIndex++), pattern.Substring(pIndex), caseSensitive)) + { + return true; + } + } + + return Match(target, pattern.Substring(pIndex), caseSensitive); + } + + // match any one char. + case '?': + { + // check if end of string when looking for a single character. + if (tIndex >= target.Length) + { + return false; + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length - 1) + { + return false; + } + + tIndex++; + break; + } + + // match char set + case '[': + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (tIndex > target.Length) + { + return false; // syntax + } + + var l = '\0'; + + // match a char if NOT in set [] + if (pattern[pIndex] == '!') + { + ++pIndex; + + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then + { + break; // no match found + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + return false; // if in range, return false + } + } + + l = p; + + if (c == p) // if char matches this element + { + return false; // return false + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + } + // match if char is in set [] + else + { + p = ConvertCase(pattern[pIndex++], caseSensitive); + + while (pIndex < pattern.Length) + { + if (p == ']') // if end of char set, then no match found + { + return false; + } + + if (p == '-') + { + // check a range of chars? + p = ConvertCase(pattern[pIndex], caseSensitive); + + // get high limit of range + if (pIndex > pattern.Length || p == ']') + { + return false; // syntax + } + + if (c >= l && c <= p) + { + break; // if in range, move on + } + } + + l = p; + + if (c == p) // if char matches this element move on + { + break; + } + + p = ConvertCase(pattern[pIndex++], caseSensitive); + } + + while (pIndex < pattern.Length && p != ']') // got a match in char set skip to end of set + { + p = pattern[pIndex++]; + } + } + + break; + } + + // match digit. + case '#': + { + c = target[tIndex++]; + + if (!char.IsDigit(c)) + { + return false; // not a digit + } + + break; + } + + // match exact char. + default: + { + c = ConvertCase(target[tIndex++], caseSensitive); + + if (c != p) // check for exact char + { + return false; // not a match + } + + // check if end of pattern and still string data left. + if (pIndex >= pattern.Length && tIndex < target.Length - 1) + { + return false; + } + + break; + } + } + } + + return true; + } + + #endregion + + #region Private Methods + + private static char ConvertCase(char c, bool caseSensitive) + { + return (caseSensitive) ? c : char.ToUpper(c); + } + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcFactory.cs b/Technosoftware/DaAeHdaClient/OpcFactory.cs new file mode 100644 index 0000000..05b4114 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcFactory.cs @@ -0,0 +1,124 @@ +#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 +{ + + /// + /// The default class used to instantiate server objects. + /// + [Serializable] + public class OpcFactory : IOpcFactory, ISerializable, ICloneable + { + #region Class Names + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string SystemType = "SystemType"; + } + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with the type of the servers it can instantiate. + /// + /// The System.Type of the server object that the factory can create. + public OpcFactory(Type systemType) + { + SystemType = systemType; + } + + /// + /// Maybe overridden to release unmanaged resources. + /// + public virtual void Dispose() + { + // do nothing. + } + + /// + /// Construct a server by de-serializing its OpcUrl from the stream. + /// + protected OpcFactory(SerializationInfo info, StreamingContext context) + { + SystemType = (Type)info.GetValue(Names.SystemType, typeof(Type)); + } + #endregion + + #region Properties + /// + /// The system type used to instantiate the remote server object. + /// + protected Type SystemType { get; set; } + + /// + /// Can be used to force OPC DA 2.0 even if OPC DA 3.0 server features are available + /// + public bool ForceDa20Usage { get; set; } + + /// + /// List of supported OPC specifications + /// + public IList SupportedSpecifications { get; set; } + #endregion + + #region ISerializable + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.SystemType, SystemType); + } + #endregion + + #region ICloneable + /// + /// Returns a clone of the factory. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + + #region IOpcFactory + /// + /// Creates a new instance of the server. + /// + public virtual IOpcServer CreateInstance(OpcUrl url, OpcConnectData connectData) + { + var server = (IOpcServer)Activator.CreateInstance(SystemType, new object[] { url, connectData }); + + return server; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcItem.cs b/Technosoftware/DaAeHdaClient/OpcItem.cs new file mode 100644 index 0000000..878f203 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcItem.cs @@ -0,0 +1,114 @@ +#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.Text; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A unique item identifier. + /// + [Serializable] + public class OpcItem : ICloneable + { + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with default values. + /// + public OpcItem() { } + + /// + /// Initializes the object with the specified item name. + /// + public OpcItem(string itemName) + { + ItemPath = null; + ItemName = itemName; + } + + /// + /// Initializes the object with the specified item path and item name. + /// + public OpcItem(string itemPath, string itemName) + { + ItemPath = itemPath; + ItemName = itemName; + } + + /// + /// Initializes the object with the specified item identifier. + /// + public OpcItem(OpcItem itemId) + { + if (itemId != null) + { + ItemPath = itemId.ItemPath; + ItemName = itemId.ItemName; + ClientHandle = itemId.ClientHandle; + ServerHandle = itemId.ServerHandle; + } + } + #endregion + + #region Properties + /// + /// The primary identifier for an item within the server namespace. + /// + public string ItemName { get; set; } + + /// + /// An secondary identifier for an item within the server namespace. + /// + public string ItemPath { get; set; } + + /// + /// A unique item identifier assigned by the client. + /// + public object ClientHandle { get; set; } + + /// + /// A unique item identifier assigned by the server. + /// + public object ServerHandle { get; set; } + + /// + /// Create a string that can be used as index in a hash table for the item. + /// + public string Key => + new StringBuilder(64) + .Append(ItemName ?? "null") + .Append(Environment.NewLine) + .Append(ItemPath ?? "null") + .ToString(); + #endregion + + #region ICloneable Members + /// + /// Creates a shallow copy of the object. + /// + public virtual object Clone() { return MemberwiseClone(); } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcItemCollection.cs b/Technosoftware/DaAeHdaClient/OpcItemCollection.cs new file mode 100644 index 0000000..9215e9d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcItemCollection.cs @@ -0,0 +1,157 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A collection of item identifiers. + /// + [Serializable] + public class OpcItemCollection : ICloneable, ICollection + { + /// + /// Creates an empty collection. + /// + public OpcItemCollection() + { + // do nothing. + } + + /// + /// Initializes the object with any ItemIdentifiers contained in the collection. + /// + /// A collection containing item ids. + public OpcItemCollection(ICollection collection) + { + Init(collection); + } + + /// + /// Returns the itemID at the specified index. + /// + public OpcItem this[int index] + { + get => itemIds_[index]; + set => itemIds_[index] = value; + } + + /// + /// Initializes the object with any item ids contained in the collection. + /// + /// A collection containing item ids. + public void Init(ICollection collection) + { + Clear(); + + if (collection != null) + { + var itemIds = new ArrayList(collection.Count); + + foreach (var value in collection) + { + if (value is OpcItem item) + { + itemIds.Add(item.Clone()); + } + } + + itemIds_ = (OpcItem[])itemIds.ToArray(typeof(OpcItem)); + } + } + + /// + /// Removes all itemIDs in the collection. + /// + public void Clear() + { + itemIds_ = new OpcItem[0]; + } + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return new OpcItemCollection(this); + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => itemIds_?.Length ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + itemIds_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(OpcItem[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return itemIds_.GetEnumerator(); + } + #endregion + + #region Private Members + private OpcItem[] itemIds_ = new OpcItem[0]; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcItemResult.cs b/Technosoftware/DaAeHdaClient/OpcItemResult.cs new file mode 100644 index 0000000..cc38eb3 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcItemResult.cs @@ -0,0 +1,126 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A result code associated with a unique item identifier. + /// + [Serializable] + public class OpcItemResult : OpcItem, IOpcResult + { + #region Fields + private OpcResult result_ = OpcResult.S_OK; + private string diagnosticInfo_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initialize object with default values. + /// + public OpcItemResult() { } + + /// + /// Initialize object with the specified OpcItem object. + /// + public OpcItemResult(OpcItem item) + : base(item) + { + } + + /// + /// Initialize object with the specified IdentifiedResult object. + /// + public OpcItemResult(OpcItemResult item) + : base(item) + { + if (item != null) + { + Result = item.Result; + DiagnosticInfo = item.DiagnosticInfo; + } + } + + /// + /// Initializes the object with the specified item name and result code. + /// + public OpcItemResult(string itemName, OpcResult resultId) + : base(itemName) + { + Result = resultId; + } + + /// + /// Initialize object with the specified item name, result code and diagnostic info. + /// + public OpcItemResult(string itemName, OpcResult resultId, string diagnosticInfo) + : base(itemName) + { + Result = resultId; + DiagnosticInfo = diagnosticInfo; + } + + /// + /// Initialize object with the specified OpcItem and result code. + /// + public OpcItemResult(OpcItem item, OpcResult resultId) + : base(item) + { + Result = resultId; + } + + /// + /// Initialize object with the specified OpcItem, result code and diagnostic info. + /// + public OpcItemResult(OpcItem item, OpcResult resultId, string diagnosticInfo) + : base(item) + { + Result = resultId; + DiagnosticInfo = diagnosticInfo; + } + #endregion + + #region IOpcResult Members + /// + /// The error id for the result of an operation on an item. + /// + public OpcResult Result + { + get => result_; + set => result_ = value; + } + + /// + /// Vendor specific diagnostic information (not the localized error text). + /// + public string DiagnosticInfo + { + get => diagnosticInfo_; + set => diagnosticInfo_ = value; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcItemResultCollection.cs b/Technosoftware/DaAeHdaClient/OpcItemResultCollection.cs new file mode 100644 index 0000000..87a5f33 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcItemResultCollection.cs @@ -0,0 +1,156 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A collection of identified results. + /// + [Serializable] + public class OpcItemResultCollection : ICloneable, ICollection + { + /// + /// Returns the IdentifiedResult at the specified index. + /// + public OpcItemResult this[int index] + { + get => itemResults_[index]; + set => itemResults_[index] = value; + } + + /// + /// Creates an empty collection. + /// + public OpcItemResultCollection() + { + // do nothing. + } + + /// + /// Initializes the object with any IdentifiedResults contained in the collection. + /// + /// A collection containing item ids. + public OpcItemResultCollection(ICollection collection) + { + Init(collection); + } + + /// + /// Initializes the object with any item ids contained in the collection. + /// + /// A collection containing item ids. + public void Init(ICollection collection) + { + Clear(); + + if (collection != null) + { + var itemIDs = new ArrayList(collection.Count); + + foreach (var value in collection) + { + if (value is OpcItemResult result) + { + itemIDs.Add(result.Clone()); + } + } + + itemResults_ = (OpcItemResult[])itemIDs.ToArray(typeof(OpcItemResult)); + } + } + + /// + /// Removes all itemIDs in the collection. + /// + public void Clear() + { + itemResults_ = new OpcItemResult[0]; + } + + #region ICloneable Members + /// + /// Creates a deep copy of the object. + /// + public virtual object Clone() + { + return new OpcItemResultCollection(this); + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public int Count => itemResults_?.Length ?? 0; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(Array array, int index) + { + itemResults_?.CopyTo(array, index); + } + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public void CopyTo(OpcItemResult[] array, int index) + { + CopyTo((Array)array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return itemResults_.GetEnumerator(); + } + #endregion + + #region Private Members + private OpcItemResult[] itemResults_ = new OpcItemResult[0]; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcNamespace.cs b/Technosoftware/DaAeHdaClient/OpcNamespace.cs new file mode 100644 index 0000000..065329f --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcNamespace.cs @@ -0,0 +1,55 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Declares constants for common XML Schema and OPC namespaces. + /// + public class OpcNamespace + { + /// XML Schema + public const string XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; + /// XML Schema Instance + public const string XML_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance"; + /// OPC Alarmes & Events + public const string OPC_ALARM_AND_EVENTS = "http://opcfoundation.org/AlarmAndEvents/"; + /// OPC Complex Data + public const string OPC_COMPLEX_DATA = "http://opcfoundation.org/ComplexData/"; + /// OPC Data Exchange + public const string OPC_DATA_EXCHANGE = "http://opcfoundation.org/DataExchange/"; + /// OPC Data Access + public const string OPC_DATA_ACCESS = "http://opcfoundation.org/DataAccess/"; + /// OPC Historical Data Access + public const string OPC_HISTORICAL_DATA_ACCESS = "http://opcfoundation.org/HistoricalDataAccess/"; + /// OPC Binary 1.0 + public const string OPC_BINARY = "http://opcfoundation.org/OPCBinary/1.0/"; + /// OPC XML-DA 1.0 + public const string OPC_DATA_ACCESS_XML10 = "http://opcfoundation.org/webservices/XMLDA/1.0/"; + /// OPC UA 1.0 + public const string OPC_UA10 = "http://opcfoundation.org/webservices/UA/1.0/"; + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcReadOnlyCollection.cs b/Technosoftware/DaAeHdaClient/OpcReadOnlyCollection.cs new file mode 100644 index 0000000..806569d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcReadOnlyCollection.cs @@ -0,0 +1,191 @@ +#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; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A read only collection class which can be used to expose arrays as properties of classes. + /// + [Serializable] + public class OpcReadOnlyCollection : ICollection, ICloneable, ISerializable + { + #region Fields + private Array array_; + #endregion + + #region Public Methods + /// + /// An indexer for the collection. + /// + public virtual object this[int index] => array_.GetValue(index); + + /// + /// Returns a copy of the collection as an array. + /// + public virtual Array ToArray() + { + return (Array)OpcConvert.Clone(array_); + } + #endregion + + #region Protected Interface + /// + ///Creates a collection that wraps the specified array instance. + /// + protected OpcReadOnlyCollection(Array array) + { + Array = array; + } + + /// + /// The array instance exposed by the collection. + /// + protected virtual Array Array + { + get => array_; + + set + { + array_ = value; + + if (array_ == null) + { + array_ = new object[0]; + } + } + } + #endregion + + #region ISerializable Members + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Array = "AR"; + } + + /// + /// Construct a server by de-serializing its OpcUrl from the stream. + /// + protected OpcReadOnlyCollection(SerializationInfo info, StreamingContext context) + { + array_ = (Array)info.GetValue(Names.Array, typeof(Array)); + } + + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.Array, array_); + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public virtual int Count => array_.Length; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public virtual void CopyTo(Array array, int index) + { + if (array_ != null) + { + array_.CopyTo(array, index); + } + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual object SyncRoot => this; + + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return array_.GetEnumerator(); + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the collection. + /// + public virtual object Clone() + { + var clone = (OpcReadOnlyCollection)MemberwiseClone(); + + var array = new ArrayList(array_.Length); + + // clone the elements and determine the element type. + Type elementType = null; + + for (var ii = 0; ii < array_.Length; ii++) + { + var element = array_.GetValue(ii); + + if (elementType == null) + { + elementType = element.GetType(); + } + else if (elementType != typeof(object)) + { + while (!(elementType is null) && !elementType.IsInstanceOfType(element)) + { + elementType = elementType.BaseType; + } + } + + array.Add(OpcConvert.Clone(element)); + } + + // convert array list to an array. + clone.Array = array.ToArray(elementType); + + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcReadOnlyDictionary.cs b/Technosoftware/DaAeHdaClient/OpcReadOnlyDictionary.cs new file mode 100644 index 0000000..dd47b71 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcReadOnlyDictionary.cs @@ -0,0 +1,263 @@ +#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; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A read only dictionary class which can be used to expose arrays as properties of classes. + /// + [Serializable] + public class OpcReadOnlyDictionary : IDictionary, ISerializable + { + #region Protected Interface + /// + ///Creates a collection that wraps the specified array instance. + /// + protected OpcReadOnlyDictionary(Hashtable dictionary) + { + Dictionary = dictionary; + } + + /// + /// The array instance exposed by the collection. + /// + protected virtual Hashtable Dictionary + { + get => dictionary_; + + set + { + dictionary_ = value; + + if (dictionary_ == null) + { + dictionary_ = new Hashtable(); + } + } + } + #endregion + + #region ISerializable Members + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Count = "CT"; + internal const string Key = "KY"; + internal const string Value = "VA"; + } + + /// + /// Construct a server by de-serializing its OpcUrl from the stream. + /// + protected OpcReadOnlyDictionary(SerializationInfo info, StreamingContext context) + { + var count = (int)info.GetValue(Names.Count, typeof(int)); + + dictionary_ = new Hashtable(); + + for (var ii = 0; ii < count; ii++) + { + var key = info.GetValue(Names.Key + ii.ToString(), typeof(object)); + var value = info.GetValue(Names.Value + ii.ToString(), typeof(object)); + + if (key != null) + { + dictionary_[key] = value; + } + } + } + + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.Count, dictionary_.Count); + + var ii = 0; + + var enumerator = dictionary_.GetEnumerator(); + + while (enumerator.MoveNext()) + { + info.AddValue(Names.Key + ii.ToString(), enumerator.Key); + info.AddValue(Names.Value + ii.ToString(), enumerator.Value); + + ii++; + } + } + #endregion + + #region IDictionary Members + /// + /// Gets a value indicating whether the IDictionary is read-only. + /// + public virtual bool IsReadOnly => true; + + /// + /// Returns an IDictionaryEnumerator for the IDictionary. + /// + public virtual IDictionaryEnumerator GetEnumerator() + { + return dictionary_.GetEnumerator(); + } + + /// + /// Gets or sets the element with the specified key. + /// + public virtual object this[object key] + { + get => dictionary_[key]; + + set => throw new InvalidOperationException(ReadOnlyDictionary); + } + + /// + /// Removes the element with the specified key from the IDictionary. + /// + public virtual void Remove(object key) + { + throw new InvalidOperationException(ReadOnlyDictionary); + } + + /// + /// Determines whether the IDictionary contains an element with the specified key. + /// + public virtual bool Contains(object key) + { + return dictionary_.Contains(key); + } + + /// + /// Removes all elements from the IDictionary. + /// + public virtual void Clear() + { + throw new InvalidOperationException(ReadOnlyDictionary); + } + + /// + /// Gets an ICollection containing the values in the IDictionary. + /// + public virtual ICollection Values => dictionary_.Values; + + /// + /// Adds an element with the provided key and value to the IDictionary. + /// + public void Add(object key, object value) + { + throw new InvalidOperationException(ReadOnlyDictionary); + } + + /// + /// Gets an ICollection containing the keys of the IDictionary. + /// + public virtual ICollection Keys => dictionary_.Keys; + + /// + /// Gets a value indicating whether the IDictionary has a fixed size. + /// + public virtual bool IsFixedSize => false; + + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public virtual int Count => dictionary_.Count; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public virtual void CopyTo(Array array, int index) + { + if (dictionary_ != null) + { + dictionary_.CopyTo(array, index); + } + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual object SyncRoot => this; + + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the collection. + /// + public virtual object Clone() + { + var clone = (OpcReadOnlyDictionary)MemberwiseClone(); + + // clone contents of hashtable. + var dictionary = new Hashtable(); + + var enumerator = dictionary_.GetEnumerator(); + + while (enumerator.MoveNext()) + { + dictionary.Add(OpcConvert.Clone(enumerator.Key), OpcConvert.Clone(enumerator.Value)); + } + + clone.dictionary_ = dictionary; + + // return clone. + return clone; + } + #endregion + + #region Private Members + private Hashtable dictionary_ = new Hashtable(); + private const string ReadOnlyDictionary = "Cannot change the contents of a read-only dictionary"; + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcResult.cs b/Technosoftware/DaAeHdaClient/OpcResult.cs new file mode 100644 index 0000000..ee5dfdf --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcResult.cs @@ -0,0 +1,811 @@ +#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.Xml; +using System.Runtime.Serialization; +using System.Runtime.InteropServices; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// Contains an unique identifier for an OPC specific result code. + /// Most functions raises a OpcResultException if an error occur. + /// OpcResultException Class + [Serializable] + public struct OpcResult : ISerializable + { + #region Serialization Functions + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Name = "NA"; + internal const string Namespace = "NS"; + internal const string Code = "CO"; + } + + // During deserialization, SerializationInfo is passed to the class using the constructor provided for this purpose. Any visibility + // constraints placed on the constructor are ignored when the object is deserialized; so you can mark the class as public, + // protected, internal, or private. However, it is best practice to make the constructor protected unless the class is sealed, in which case + // the constructor should be marked private. The constructor should also perform thorough input validation. To avoid misuse by malicious code, + // the constructor should enforce the same security checks and permissions required to obtain an instance of the class using any other + // constructor. + /// + /// Construct a server by de-serializing its URL from the stream. + /// + public OpcResult(SerializationInfo info, StreamingContext context) + { + var name = (string)info.GetValue(Names.Name, typeof(string)); + var ns = (string)info.GetValue(Names.Namespace, typeof(string)); + name_ = new XmlQualifiedName(name, ns); + code_ = (int)info.GetValue(Names.Code, typeof(int)); + type_ = CodeType.OpcSysCode; + caller_ = null; + message_ = null; + } + + /// + /// Serializes a server into a stream. + /// + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (name_ != null) + { + info.AddValue(Names.Name, name_.Name); + info.AddValue(Names.Namespace, name_.Namespace); + } + info.AddValue(Names.Code, code_); + } + #endregion + + /// + /// Specifies the type identifier of the result or error code. + /// + public enum CodeType + { + /// + /// System specific code (result/error) returned by a system function. + /// + SysCode, + /// + /// System specific code (result/error) returned by an OPC function. + /// + OpcSysCode, + + /// + /// Data Access specific code (result/error) returned by an OPC function. + /// + DaCode, + + /// + /// Alarms & Events specific code (result/error) returned by an OPC function. + /// + AeCode, + + /// + /// XML-DA specific code (result/error) returned by an OPC function. + /// + XdaCode + + }; + + /// + /// Specifies the type of function call which returned the result or error code. This enumeration values are used only by the constructor of the OpcResult object. + /// + public enum FuncCallType + { + /// + /// Identifies the code (result/error) passed to the constructor as a result of a system function. + /// + SysFuncCall, + + /// + /// Identifies the code (result/error) passed to the constructor as a result of an OPC Data Access function. + /// + DaFuncCall, + + /// + /// Identifies the code (result/error) passed to the constructor as a result of an OPC Alarms & Events function. + /// + AeFuncCall, + + }; + + /// + /// Used for result codes identified by a qualified name. + /// + public XmlQualifiedName Name => name_; + + /// + /// Used for result codes identified by a integer. + /// + public int Code => code_; + + /// + /// Returns true if the objects are equal. + /// + public static bool operator ==(OpcResult a, OpcResult b) + { + return a.Equals(b); + } + + /// + /// Returns true if the objects are not equal. + /// + public static bool operator !=(OpcResult a, OpcResult b) + { + return !a.Equals(b); + } + + /// + /// Checks for the 'S_' prefix that indicates a success condition. + /// + public bool Succeeded() + { + if (Code != -1) return (Code >= 0); + if (Name != null) return Name.Name.StartsWith("S_"); + return false; + } + + /// + /// Checks for the 'E_' prefix that indicates an error condition. + /// + public bool Failed() + { + if (Code != -1) return (Code < 0); + if (Name != null) return Name.Name.StartsWith("E_"); + return false; + } + + /// + /// Retrieves the type identifier of the code passed to the constructor. + /// + /// CodeType of the HRESULT code + internal CodeType Type() + { + return (type_); + } + + /// + /// Indicates whether the result code represents an error value. + /// + /// This function returns true if the associated result code is an error code. + public bool IsError() + { + return (code_ < 0); + } + + /// + /// Indicates whether the result code represents an error value. + /// + internal static bool IsError(int hResult) + { + return (hResult < 0); + } + + /// + /// Indicates whether the result code represents an error free value. + /// + /// This function returns true if the associated result code is an error free value. + public bool IsSuccess() + { + return (code_ >= 0); + + } + + /// + /// Indicates whether the result code represents an error free value. + /// + internal static bool IsSuccess(int hResult) + { + return (hResult >= 0); + + } + + /// + /// Indicates whether the result code represents an error value. + /// + /// This function returns true if the associated result code is 0. + public bool IsOk() + { + return (code_ == 0); + } + + /// + /// Retrieves a text string with a description for the code stored in the OpcResult object. + /// + /// This method returns the description for the code recorded within the OpcResult object. If no description text is + /// found, then a generic message "Server error 0x#dwErrorCode" is returned. + public string Description() + { + switch (type_) + { + case CodeType.DaCode: + if (caller_ != null) + { + message_ = ((Technosoftware.DaAeHdaClient.Da.TsCDaServer)caller_).GetErrorText(((Technosoftware.DaAeHdaClient.Da.TsCDaServer)caller_).GetLocale(), this); + } + else + { + message_ = $"Server error 0x{code_,0:X}"; + } + break; + + default: + message_ = GetSystemMessage(code_, LOCALE_SYSTEM_DEFAULT) ?? + $"Server error 0x{code_,0:X}"; + break; + } + return message_; + } + + #region Constructors + /// + /// Constructs a OpcResult object. + /// + /// The code returned by a system function or OPC function. The code can be retrieved with the member function Code() and a description text can be retrieved with the member function . + /// Specifies the type of function which has returned the code. This parameter is used to create the code type which can be retrieved with the member function . + /// Object which caused the error. Can be null + /// + public OpcResult(int hResult, FuncCallType eFuncType, object caller) + { + name_ = null; + message_ = null; + code_ = hResult; + caller_ = caller; + type_ = CodeType.OpcSysCode; + + if (eFuncType == FuncCallType.SysFuncCall) + { + type_ = CodeType.SysCode; // System specific errror returned by a system function + } + else if ((((hResult) >> 16) & 0x1fff) == 0x4) + { // FACILITY_ITF 0x4 + if (eFuncType == FuncCallType.DaFuncCall) type_ = CodeType.DaCode; + else type_ = CodeType.AeCode; + } + else + { + type_ = CodeType.OpcSysCode; // System specific error returned by an OPC function + } + } + + /// + /// Constructs a OpcResult object. + /// + /// The code returned by a system function or OPC function. The code can be retrieved with the member function Code() and a description text can be retrieved with the member function . + /// Specifies the type of function which has returned the code. This parameter is used to create the code type which can be retrieved with the member function . + /// Object which caused the error. Can be null + /// + public OpcResult(OpcResult resultId, FuncCallType eFuncType, object caller) + { + name_ = null; + message_ = null; + code_ = resultId.Code; + caller_ = caller; + type_ = CodeType.OpcSysCode; + + if (eFuncType == FuncCallType.SysFuncCall) + { + type_ = CodeType.SysCode; // System specific error returned by a system function + } + else if ((((resultId.Code) >> 16) & 0x1fff) == 0x4) + { // FACILITY_ITF 0x4 + if (eFuncType == FuncCallType.DaFuncCall) type_ = CodeType.DaCode; + else type_ = CodeType.AeCode; + } + else + { + type_ = CodeType.OpcSysCode; // System specific error returned by an OPC function + } + } + + /// + /// Initializes a result code identified by a qualified name. + /// + internal OpcResult(XmlQualifiedName name) + { + name_ = name; + message_ = null; + code_ = -1; + type_ = CodeType.XdaCode; + caller_ = null; + } + + /// + /// Initializes a result code identified by an integer. + /// + public OpcResult(long code) + { + name_ = null; + message_ = null; + + + if (code > int.MaxValue) + { + code = -(((long)uint.MaxValue) + 1 - code); + } + + code_ = (int)code; + + type_ = CodeType.OpcSysCode; + caller_ = null; + + } + + /// + /// Initializes a result code identified by a qualified name. + /// + public OpcResult(string name, string ns) + { + name_ = new XmlQualifiedName(name, ns); + message_ = null; + + code_ = -1; + type_ = CodeType.OpcSysCode; + caller_ = null; + + } + + /// + /// Initializes a result code identified by a qualified name and a specific result code. + /// + public OpcResult(string name, string ns, long code) + { + name_ = new XmlQualifiedName(name, ns); + if (code > int.MaxValue) + { + code = -(((long)uint.MaxValue) + 1 - code); + } + + code_ = (int)code; + type_ = CodeType.OpcSysCode; + caller_ = null; + message_ = null; + + } + + /// + /// Initializes a result code with a general result code and a specific result code. + /// + public OpcResult(OpcResult resultId, long code) + { + name_ = resultId.Name; + + if (code > int.MaxValue) + { + code = -(((long)uint.MaxValue) + 1 - code); + } + + code_ = (int)code; + type_ = CodeType.OpcSysCode; + caller_ = null; + message_ = null; + + } + #endregion + + #region Private Methods + /// + /// The constant used to selected the default locale. + /// + internal const int LOCALE_SYSTEM_DEFAULT = 0x800; + + /// + /// The WIN32 user default locale. + /// + public const int LOCALE_USER_DEFAULT = 0x400; + + private const int MAX_MESSAGE_LENGTH = 1024; + + private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; + private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + [DllImport("Kernel32.dll")] + private static extern int FormatMessageW( + int dwFlags, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + IntPtr lpBuffer, + int nSize, + IntPtr Arguments); + + [DllImport("Kernel32.dll")] + private static extern int GetSystemDefaultLangID(); + + [DllImport("Kernel32.dll")] + private static extern int GetUserDefaultLangID(); + + /// + /// Retrieves the system message text for the specified error. + /// + private static string GetSystemMessage(int error) + { + var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH); + + var result = FormatMessageW( + (int)(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_SYSTEM), + IntPtr.Zero, + error, + 0, + buffer, + MAX_MESSAGE_LENGTH - 1, + IntPtr.Zero); + + var msg = Marshal.PtrToStringUni(buffer); + Marshal.FreeCoTaskMem(buffer); + + if (msg != null && msg.Length > 0) + { + return msg; + } + + return string.Format("0x{0,0:X}", error); + } + + /// + /// Retrieves the system message text for the specified error. + /// + public static string GetSystemMessage(int error, int localeId) + { + int langId; + switch (localeId) + { + case LOCALE_SYSTEM_DEFAULT: + { + langId = GetSystemDefaultLangID(); + break; + } + + case LOCALE_USER_DEFAULT: + { + langId = GetUserDefaultLangID(); + break; + } + + default: + { + langId = (0xFFFF & localeId); + break; + } + } + + var buffer = Marshal.AllocCoTaskMem(MAX_MESSAGE_LENGTH); + + var result = FormatMessageW( + (int)FORMAT_MESSAGE_FROM_SYSTEM, + IntPtr.Zero, + error, + langId, + buffer, + MAX_MESSAGE_LENGTH - 1, + IntPtr.Zero); + + if (result > 0) + { + var msg = Marshal.PtrToStringUni(buffer); + Marshal.FreeCoTaskMem(buffer); + + if (!string.IsNullOrEmpty(msg)) + { + return msg.Trim(); + } + } + + return $"0x{error:X8}"; + } + #endregion + + #region Object Method Overrides + /// + /// Returns true if the target object is equal to the object. + /// + public override bool Equals(object target) + { + if (target != null && (target is OpcResult)) + { + var resultId = (OpcResult)target; + + // compare by integer if both specify valid integers. + if (resultId.Code != -1 && Code != -1) + { + return (resultId.Code == Code) && (resultId.Name == Name); + } + + // compare by name if both specify valid names. + if (resultId.Name != null && Name != null) + { + return (resultId.Name == Name); + } + } + + return false; + } + + /// + /// Formats the result identifier as a string. + /// + public override string ToString() + { + if (Name != null) return Name.Name; + return $"0x{Code,0:X}"; + } + + /// + /// Returns a useful hash code for the object. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + #endregion + + #region Private Members + private XmlQualifiedName name_; + private int code_; + private CodeType type_; + private object caller_; + private string message_; + + + #endregion + + /// The function was successful (Return Code: 0x00000000). + public static readonly OpcResult S_OK = new OpcResult("S_OK", OpcNamespace.OPC_DATA_ACCESS, 0x00000000); + /// The function completed with an error (Return Code: 0x00000001). + public static readonly OpcResult S_FALSE = new OpcResult("S_FALSE", OpcNamespace.OPC_DATA_ACCESS, 0x00000001); + /// The function was unsuccessfull (Return Code: 0x80004005). + public static readonly OpcResult E_FAIL = new OpcResult("E_FAIL", OpcNamespace.OPC_DATA_ACCESS, 0x80004005); + /// The interface asked for is not supported by the server (Return Code: 0x80004002). + public static readonly OpcResult E_NOINTERFACE = new OpcResult("E_NOINTERFACE", OpcNamespace.OPC_DATA_ACCESS, 0x80004002); + /// The value of one or more parameters was not valid. This is generally used in place of a more specific error where it is expected that problems are unlikely or will be easy to identify (for example when there is only one parameter) (Return Code: 0x80070057). + public static readonly OpcResult E_INVALIDARG = new OpcResult("E_INVALIDARG", OpcNamespace.OPC_DATA_ACCESS, 0x80070057); + /// Function is not implemented (Return Code: 0x80004001). + public static readonly OpcResult E_NOTIMPL = new OpcResult("E_NOTIMPL", OpcNamespace.OPC_DATA_ACCESS, 0x80004001); + /// Not enough memory to complete the requested operation. This can happen any time the server needs to allocate memory to complete the requested operation (Return Code: 0x8007000E). + public static readonly OpcResult E_OUTOFMEMORY = new OpcResult("E_OUTOFMEMORY", OpcNamespace.OPC_DATA_ACCESS, 0x8007000E); + /// Return Code: 0x80004004 + public static readonly OpcResult E_ABORT = new OpcResult("E_ABORT", OpcNamespace.OPC_DATA_ACCESS, 0x80004004); + /// NULL pointer argument. + public static readonly OpcResult E_POINTER = new OpcResult("E_POINTER", OpcNamespace.OPC_DATA_ACCESS, 0x80004003); + /// Cannot Unadvise - there is no existing connection (Return Code: 0x80040200). + public static readonly OpcResult CONNECT_E_NOCONNECTION = new OpcResult("CONNECT_E_NOCONNECTION", OpcNamespace.OPC_DATA_ACCESS, 0x80040200); + /// The operation took too long to complete. + public static readonly OpcResult E_TIMEDOUT = new OpcResult("E_TIMEDOUT", OpcNamespace.OPC_DATA_ACCESS); + /// General network error. + public static readonly OpcResult E_NETWORK_ERROR = new OpcResult("E_NETWORK_ERROR", OpcNamespace.OPC_DATA_ACCESS); + /// The server denies access (Return Code: 0x80070005). + public static readonly OpcResult E_ACCESS_DENIED = new OpcResult("E_ACCESS_DENIED", OpcNamespace.OPC_DATA_ACCESS, 0x80070005); + /// Invalid class string (Return Code: 0x800401F3). + public static readonly OpcResult CO_E_CLASSSTRING = new OpcResult("CO_E_CLASSSTRING", OpcNamespace.OPC_DATA_ACCESS, 0x800401F3); + /// The object application has been disconnected from the remoting system (Return Code: 0x800401FD). + public static readonly OpcResult CO_E_OBJNOTCONNECTED = new OpcResult("CO_E_OBJNOTCONNECTED", OpcNamespace.OPC_DATA_ACCESS, 0x800401fd); + /// + /// The server does not support the requested function with the specified parameters. + /// + public static readonly OpcResult E_NOTSUPPORTED = new OpcResult("E_NOTSUPPORTED", OpcNamespace.OPC_DATA_ACCESS); + + /// + /// Results codes for Data Access. + /// + public class Da + { + /// Indicates that not every detected change has been returned for this item. This is an indicator that servers buffer reached its limit and had to purge out the oldest data. Only the most recent data is provided. The server should only remove the oldest data for those items that have newer samples available in the buffer. This will allow single samplings of older items to be returned to the client (Return Code: 0x00040404). + public static readonly OpcResult S_DATAQUEUEOVERFLOW = new OpcResult("S_DATAQUEUEOVERFLOW", OpcNamespace.OPC_DATA_ACCESS, 0x00040404); + /// The server does not support the requested data rate but will use the closest available rate (Return Code: 0x0004000D). + public static readonly OpcResult S_UNSUPPORTEDRATE = new OpcResult("S_UNSUPPORTEDRATE", OpcNamespace.OPC_DATA_ACCESS, 0x0004000D); + /// A value passed to WRITE was accepted but the output was clamped (Return Code: 0x0004000E). + public static readonly OpcResult S_CLAMP = new OpcResult("S_CLAMP", OpcNamespace.OPC_DATA_ACCESS, 0x0004000E); + /// The value of the handle is invalid. + /// Note: a client should never pass an invalid handle to a server. If this error occurs, it is due to a programming error in the client or possibly in the server (Return Code: 0xC0040001). + public static readonly OpcResult E_INVALIDHANDLE = new OpcResult("E_INVALIDHANDLE", OpcNamespace.OPC_DATA_ACCESS, 0xC0040001); + /// The item ID is not defined in the server address space (on add or validate) or no longer exists in the server address space (for read or write) (Returnd Code: 0xC0040007). + public static readonly OpcResult E_UNKNOWN_ITEM_NAME = new OpcResult("E_UNKNOWN_ITEM_NAME", OpcNamespace.OPC_DATA_ACCESS, 0xC0040007); + /// The item name does not conform the server's syntax. + public static readonly OpcResult E_INVALID_ITEM_NAME = new OpcResult("E_INVALID_ITEM_NAME", OpcNamespace.OPC_DATA_ACCESS); + /// The item path is no longer available in the server address space. + public static readonly OpcResult E_UNKNOWN_ITEM_PATH = new OpcResult("E_UNKNOWN_ITEM_PATH", OpcNamespace.OPC_DATA_ACCESS); + /// The item path does not conform the server's syntax + public static readonly OpcResult E_INVALID_ITEM_PATH = new OpcResult("E_INVALID_ITEM_PATH", OpcNamespace.OPC_DATA_ACCESS); + /// The passed property ID is not valid for the item (Return Code: 0xC0040203). + public static readonly OpcResult E_INVALID_PID = new OpcResult("E_INVALID_PID", OpcNamespace.OPC_DATA_ACCESS, 0xC0040203); + /// An invalid subscription handle was passed to the request. + public static readonly OpcResult E_NO_SUBSCRIPTION = new OpcResult("E_NO_SUBSCRIPTION", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The value is read only and may not be written to + public static readonly OpcResult E_READONLY = new OpcResult("E_READONLY", OpcNamespace.OPC_DATA_ACCESS); + /// The value is write-only and may not be read from or returned as part of a write response + public static readonly OpcResult E_WRITEONLY = new OpcResult("E_WRITEONLY", OpcNamespace.OPC_DATA_ACCESS); + /// The server cannot convert the data between the specified format/ requested data type and the canonical data type (Return Code: 0xC0040004). + public static readonly OpcResult E_BADTYPE = new OpcResult("E_BADTYPE", OpcNamespace.OPC_DATA_ACCESS, 0xC0040004); + /// The value was out of range (Return Code: 0xC004000B). + public static readonly OpcResult E_RANGE = new OpcResult("E_RANGE", OpcNamespace.OPC_DATA_ACCESS, 0xC004000B); + /// Duplicate name not allowed. + public static readonly OpcResult E_DUPLICATENAME = new OpcResult("E_DUPLICATENAME", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The filter string was not valid (Return Code: 0xC0040009). + public static readonly OpcResult E_INVALID_FILTER = new OpcResult("E_INVALID_FILTER", OpcNamespace.OPC_DATA_ACCESS, 0xC0040009); + /// The continuation point is not valid (Return Code: 0xC0040403). + public static readonly OpcResult E_INVALIDCONTINUATIONPOINT = new OpcResult("E_INVALIDCONTINUATIONPOINT", OpcNamespace.OPC_DATA_ACCESS, 0xC0040403); + /// The server does not support writing of quality and/or timestamp. + public static readonly OpcResult E_NO_WRITEQT = new OpcResult("E_NO_WRITEQT", OpcNamespace.OPC_DATA_ACCESS); + /// The item deadband has not been set for this item (Return Code: 0xC0040400). + public static readonly OpcResult E_NO_ITEM_DEADBAND = new OpcResult("E_NO_ITEM_DEADBAND", OpcNamespace.OPC_DATA_ACCESS, 0xC0040400); + /// + public static readonly OpcResult E_NO_ITEM_SAMPLING = new OpcResult("E_NO_ITEM_SAMPLING", OpcNamespace.OPC_DATA_ACCESS); + /// The server does not support buffering of data items that are collected at a faster rate than the subscription update rate (Return Code: 0xC0040402) + public static readonly OpcResult E_NO_ITEM_BUFFERING = new OpcResult("E_NO_ITEM_BUFFERING", OpcNamespace.OPC_DATA_ACCESS, 0xC0040402); + // + //public static readonly OpcResult E_DUPPLICATE_FULLITEMNAME = new OpcResult("E_DUPPLICATE_FULLITEMNAME", Namespace.OPC_DATA_ACCESS_XML10); + } + + /// + /// Results codes for XML-DA. + /// + public class Xda + { + + /// The function was successful. + public static readonly OpcResult S_OK = new OpcResult("S_OK", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// A value passed to WRITE was accepted but the output was clamped. + public static readonly OpcResult S_CLAMP = new OpcResult("S_CLAMP", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// Indicates that not every detected change has been returned for this item. This is an indicator that servers buffer reached its limit and had to purge out the oldest data. Only the most recent data is provided. The server should only remove the oldest data for those items that have newer samples available in the buffer. This will allow single samplings of older items to be returned to the client. + public static readonly OpcResult S_DATAQUEUEOVERFLOW = new OpcResult("S_DATAQUEUEOVERFLOW", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The server does not support the requested data rate but will use the closest available rate. + public static readonly OpcResult S_UNSUPPORTEDRATE = new OpcResult("S_UNSUPPORTEDRATE", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The server denies access. + public static readonly OpcResult E_ACCESS_DENIED = new OpcResult("E_ACCESS_DENIED", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// A refresh is currently in progress. + public static readonly OpcResult E_BUSY = new OpcResult("E_BUSY", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The function was unsuccessfull. + public static readonly OpcResult E_FAIL = new OpcResult("E_FAIL", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The continuation point is not valid. + public static readonly OpcResult E_INVALIDCONTINUATIONPOINT = new OpcResult("E_INVALIDCONTINUATIONPOINT", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The filter string is not valid. + public static readonly OpcResult E_INVALIDFILTER = new OpcResult("E_INVALIDFILTER", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The hold time is too long (determined by server). + public static readonly OpcResult E_INVALIDHOLDTIME = new OpcResult("E_INVALIDHOLDTIME", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The item name does not conform the server's syntax. + public static readonly OpcResult E_INVALIDITEMNAME = new OpcResult("E_INVALIDITEMNAME", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The item path does not conform the server's syntax + public static readonly OpcResult E_INVALIDITEMPATH = new OpcResult("E_INVALIDITEMPATH", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The passed property ID is not valid for the item. + public static readonly OpcResult E_INVALIDPID = new OpcResult("E_INVALIDPID", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// An invalid subscription handle was passed to the request. + public static readonly OpcResult E_NO_SUBSCRIPTION = new OpcResult("E_NO_SUBSCRIPTION", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The server does not support the requested function with the specified parameters. + public static readonly OpcResult E_NOT_SUPPORTED = new OpcResult("E_NOT_SUPPORTED", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// Not enough memory to complete the requested operation. This can happen any time the server needs to allocate memory to complete the requested operation. + public static readonly OpcResult E_OUTOFMEMORY = new OpcResult("E_OUTOFMEMORY", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The value was out of range. + public static readonly OpcResult E_RANGE = new OpcResult("E_RANGE", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The value is read only and may not be written to + public static readonly OpcResult E_READONLY = new OpcResult("E_READONLY", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The operation could not complete due to an abnormal server state. + public static readonly OpcResult E_SERVERSTATE = new OpcResult("E_SERVERSTATE", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The operation took too long to complete. + public static readonly OpcResult E_TIMEDOUT = new OpcResult("E_TIMEDOUT", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The item ID is not defined in the server address space (on add or validate) or no longer exists in the server address space (for read or write). + public static readonly OpcResult E_UNKNOWNITEMNAME = new OpcResult("E_UNKNOWNITEMNAME", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The item path is no longer available in the server address space. + public static readonly OpcResult E_UNKNOWNITEMPATH = new OpcResult("E_UNKNOWNITEMPATH", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The value is write-only and may not be read from or returned as part of a write response + public static readonly OpcResult E_WRITEONLY = new OpcResult("E_WRITEONLY", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The value of one or more parameters was not valid. This is generally used in place of a more specific error where it is expected that problems are unlikely or will be easy to identify (for example when there is only one parameter). + public static readonly OpcResult E_INVALIDARG = new OpcResult("E_INVALIDARG", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// The server cannot convert the data between the specified format/ requested data type and the canonical data type. + public static readonly OpcResult E_BADTYPE = new OpcResult("E_BADTYPE", OpcNamespace.OPC_DATA_ACCESS_XML10); + /// Item whith this FullItemName was allready defined. + public static readonly OpcResult E_DUPPLICATE_FULLITEMNAME = new OpcResult("E_DUPPLICATE_FULLITEMNAME", OpcNamespace.OPC_DATA_ACCESS_XML10); + } + + /// + /// Results codes for Complex Data. + /// + public class Cpx + { + /// + /// The dictionary and/or type description for the item has changed. + /// + public static readonly OpcResult E_TYPE_CHANGED = new OpcResult("E_TYPE_CHANGED", OpcNamespace.OPC_COMPLEX_DATA); + /// + /// A data filter item with the specified name already exists. + /// + public static readonly OpcResult E_FILTER_DUPLICATE = new OpcResult("E_FILTER_DUPLICATE", OpcNamespace.OPC_COMPLEX_DATA); + /// + /// The data filter value does not conform to the server's syntax. + /// + public static readonly OpcResult E_FILTER_INVALID = new OpcResult("E_FILTER_INVALID", OpcNamespace.OPC_COMPLEX_DATA); + /// + /// An error occurred when the filter value was applied to the source data. + /// + public static readonly OpcResult E_FILTER_ERROR = new OpcResult("E_FILTER_ERROR", OpcNamespace.OPC_COMPLEX_DATA); + /// + /// The item value is empty because the data filter has excluded all fields. + /// + public static readonly OpcResult S_FILTER_NO_DATA = new OpcResult("S_FILTER_NO_DATA", OpcNamespace.OPC_COMPLEX_DATA); + } + + /// + /// Results codes for Historical Data Access. + /// + public class Hda + { + /// The server does not support writing of quality and/or timestamp. + public static readonly OpcResult E_MAXEXCEEDED = new OpcResult("E_MAXEXCEEDED", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// There is no data within the specified parameters. + public static readonly OpcResult S_NODATA = new OpcResult("S_NODATA", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// There is more data satisfying the query than was returned. + public static readonly OpcResult S_MOREDATA = new OpcResult("S_MOREDATA", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The aggregate requested is not valid. + public static readonly OpcResult E_INVALIDAGGREGATE = new OpcResult("E_INVALIDAGGREGATE", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The server only returns current values for the requested item attributes. + public static readonly OpcResult S_CURRENTVALUE = new OpcResult("S_CURRENTVALUE", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// Additional data satisfying the query was found. + public static readonly OpcResult S_EXTRADATA = new OpcResult("S_EXTRADATA", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The server does not support this filter. + public static readonly OpcResult W_NOFILTER = new OpcResult("W_NOFILTER", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The server does not support this attribute. + public static readonly OpcResult E_UNKNOWNATTRID = new OpcResult("E_UNKNOWNATTRID", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The requested aggregate is not available for the specified item. + public static readonly OpcResult E_NOT_AVAIL = new OpcResult("E_NOT_AVAIL", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The supplied value for the attribute is not a correct data type. + public static readonly OpcResult E_INVALIDDATATYPE = new OpcResult("E_INVALIDDATATYPE", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// Unable to insert - data already present. + public static readonly OpcResult E_DATAEXISTS = new OpcResult("E_DATAEXISTS", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The supplied attribute ID is not valid. + public static readonly OpcResult E_INVALIDATTRID = new OpcResult("E_INVALIDATTRID", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The server has no value for the specified time and item ID. + public static readonly OpcResult E_NODATAEXISTS = new OpcResult("E_NODATAEXISTS", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The requested insert occurred. + public static readonly OpcResult S_INSERTED = new OpcResult("S_INSERTED", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + /// The requested replace occurred. + public static readonly OpcResult S_REPLACED = new OpcResult("S_REPLACED", OpcNamespace.OPC_HISTORICAL_DATA_ACCESS); + } + + /// + /// Results codes for Alarms and Events + /// + public class Ae + { + /// The condition has already been acknowleged. + public static readonly OpcResult S_ALREADYACKED = new OpcResult("S_ALREADYACKED", OpcNamespace.OPC_ALARM_AND_EVENTS, 0x00040200); + /// The buffer time parameter was invalid. + public static readonly OpcResult S_INVALIDBUFFERTIME = new OpcResult("S_INVALIDBUFFERTIME", OpcNamespace.OPC_ALARM_AND_EVENTS, 0x00040201); + /// The max size parameter was invalid. + public static readonly OpcResult S_INVALIDMAXSIZE = new OpcResult("S_INVALIDMAXSIZE", OpcNamespace.OPC_ALARM_AND_EVENTS, 0x00040202); + /// The KeepAliveTime parameter was invalid. + public static readonly OpcResult S_INVALIDKEEPALIVETIME = new OpcResult("S_INVALIDKEEPALIVETIME", OpcNamespace.OPC_ALARM_AND_EVENTS, 0x00040203); + /// The string was not recognized as an area name. + public static readonly OpcResult E_INVALIDBRANCHNAME = new OpcResult("E_INVALIDBRANCHNAME", OpcNamespace.OPC_ALARM_AND_EVENTS, 0xC0040203); + /// The time does not match the latest active time. + public static readonly OpcResult E_INVALIDTIME = new OpcResult("E_INVALIDTIME", OpcNamespace.OPC_ALARM_AND_EVENTS, 0xC0040204); + /// A refresh is currently in progress. + public static readonly OpcResult E_BUSY = new OpcResult("E_BUSY", OpcNamespace.OPC_ALARM_AND_EVENTS, 0xC0040205); + /// Information is not available. + public static readonly OpcResult E_NOINFO = new OpcResult("E_NOINFO", OpcNamespace.OPC_ALARM_AND_EVENTS, 0xC0040206); + } + + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcResultException.cs b/Technosoftware/DaAeHdaClient/OpcResultException.cs new file mode 100644 index 0000000..7d19cc8 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcResultException.cs @@ -0,0 +1,54 @@ +#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.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// Used to raise an exception associated with a specified result code. + /// + /// The OpcResultException includes the OPC result code within the Result + /// property. + /// + /// OpcResult Structure + [Serializable] + public class OpcResultException : ApplicationException + { + /// + public OpcResult Result => result_; + + /// + public OpcResultException(OpcResult result) : base(result.Description()) { result_ = result; } + /// + public OpcResultException(OpcResult result, string message) : base(message + ": " + result.ToString() + Environment.NewLine) { result_ = result; } + /// + public OpcResultException(OpcResult result, string message, Exception e) : base(message + ": " + result.ToString() + Environment.NewLine, e) { result_ = result; } + /// + protected OpcResultException(SerializationInfo info, StreamingContext context) : base(info, context) { } + + /// + private OpcResult result_ = OpcResult.E_FAIL; + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcServer.cs b/Technosoftware/DaAeHdaClient/OpcServer.cs new file mode 100644 index 0000000..4e1c89b --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcServer.cs @@ -0,0 +1,780 @@ +#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.Globalization; +using System.Resources; +using System.Reflection; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// A base class for an in-process object used to access OPC servers. + [Serializable] + public class OpcServer : IOpcServer, ISerializable, ICloneable + { + #region Fields + + /// + /// The remote server object. + /// + internal IOpcServer Server; + + /// + /// The OpcUrl that describes the network location of the server. + /// + private OpcUrl opcUrl_; + + /// + /// The factory used to instantiate the remote server. + /// + protected IOpcFactory Factory; + + /// + /// The last set of credentials used to connect successfully to the server. + /// + private OpcConnectData connectData_; + + /// + /// A short name for the server. + /// + private string serverName_; + + /// + /// A short name for the server assigned by the client + /// + private string clientName_; + + /// + /// The default locale used by the server. + /// + private string locale_; + + /// + /// The set of locales supported by the remote server. + /// + private string[] supportedLocales_; + + /// + /// The resource manager used to access localized resources. + /// + protected ResourceManager ResourceManager; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object. + /// + public OpcServer() + { + Factory = null; + Server = null; + opcUrl_ = null; + serverName_ = null; + supportedLocales_ = null; + ResourceManager = new ResourceManager("Technosoftware.DaAeHdaClient.Resources.Strings", Assembly.GetExecutingAssembly()); + } + + /// + /// 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 OpcServer(OpcFactory factory, OpcUrl url) + { + if (factory == null) throw new ArgumentNullException(nameof(factory)); + + Factory = (IOpcFactory)factory.Clone(); + Server = null; + opcUrl_ = null; + serverName_ = null; + supportedLocales_ = null; + ResourceManager = new ResourceManager("Technosoftware.DaAeHdaClient.Resources.Strings", Assembly.GetExecutingAssembly()); + + if (url != null) SetUrl(url); + } + + /// + /// This must be called explicitly by clients to ensure the remote server is released. + /// + public virtual void Dispose() + { + if (Factory != null) + { + Factory.Dispose(); + Factory = null; + } + + if (Server != null) + { + try { Disconnect(); } + catch + { + // ignored + } + + Server = null; + } + } + #endregion + + #region Properties + /// + /// Information about an OPC Server + /// + public OpcServerDescription ServerDescription { get; set; } + + /// + /// List of supported OPC specifications + /// + public IList SupportedSpecifications { get; set; } + + /// + /// Can be used to force OPC DA 2.0 even if OPC DA 3.0 server features are available + /// + public bool ForceDa20Usage { get; set; } + #endregion + + #region Public Methods + /// + /// Finds the best matching locale given a set of supported locales. + /// + public static string FindBestLocale(string requestedLocale, string[] supportedLocales) + { + try + { + // check for direct match with requested locale. + foreach (var supportedLocale in supportedLocales) + { + if (supportedLocale == requestedLocale) + { + return requestedLocale; + } + } + + // try to find match for parent culture. + var requestedCulture = new CultureInfo(requestedLocale); + + foreach (var supportedLocale in supportedLocales) + { + try + { + var supportedCulture = new CultureInfo(supportedLocale); + + if (requestedCulture.Parent.Name == supportedCulture.Name) + { + return supportedCulture.Name; + } + } + catch + { + // ignored + } + } + + // return default locale. + return (supportedLocales.Length > 0) ? supportedLocales[0] : ""; + } + catch + { + // return default locale on any error. + return (supportedLocales != null && supportedLocales.Length > 0) ? supportedLocales[0] : ""; + } + } + #endregion + + #region Private Methods + /// + /// Updates the OpcUrl for the server. + /// + private void SetUrl(OpcUrl url) + { + if (url == null) throw new ArgumentNullException(nameof(url)); + + // cannot change the OpcUrl if the remote server is already instantiated. + if (Server != null) throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The server is already connected."); + + // copy the url. + opcUrl_ = (OpcUrl)url.Clone(); + + // construct a name for the server. + var name = ""; + + // use the host name as a base. + if (opcUrl_.HostName != null) + { + name = opcUrl_.HostName.ToLower(); + + // suppress localhost and loopback as explicit hostnames. + if (name == "localhost" || name == "127.0.0.1") + { + name = ""; + } + } + + // append the port. + if (opcUrl_.Port != 0) + { + name += $".{opcUrl_.Port}"; + } + + // add a separator. + if (name != "") { name += "."; } + + // use the prog id as the name. + if (opcUrl_.Scheme != OpcUrlScheme.HTTP) + { + var progId = opcUrl_.Path; + + var index = progId.LastIndexOf('/'); + + if (index != -1) + { + progId = progId.Substring(0, index); + } + + name += progId; + } + + // use full path without the extension as the name. + else + { + var path = opcUrl_.Path; + + // strip the file extension. + var index = path.LastIndexOf('.'); + + if (index != -1) + { + path = path.Substring(0, index); + } + + // replace slashes with dashes. + while (path.IndexOf('/') != -1) + { + path = path.Replace('/', '-'); + } + + name += path; + } + + // save the generated name in case the server name is not already set + if (string.IsNullOrEmpty(serverName_)) + { + serverName_ = name; + } + } + #endregion + + #region Protected Methods + /// + /// Returns a localized string with the specified name. + /// + protected string GetString(string name) + { + // create a culture object. + CultureInfo culture; + + try { culture = new CultureInfo(Locale); } + catch { culture = new CultureInfo(""); } + + // lookup resource string. + try { return ResourceManager.GetString(name, culture); } + catch { return null; } + } + #endregion + + #region ISerializable Members + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Name = "Name"; + internal const string Url = "Url"; + internal const string Factory = "Factory"; + } + + /// + /// Construct a server by de-serializing its OpcUrl from the stream. + /// + internal OpcServer(SerializationInfo info, StreamingContext context) + { + serverName_ = info.GetString(Names.Name); + opcUrl_ = (OpcUrl)info.GetValue(Names.Url, typeof(OpcUrl)); + Factory = (IOpcFactory)info.GetValue(Names.Factory, typeof(IOpcFactory)); + } + + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.Name, serverName_); + info.AddValue(Names.Url, opcUrl_); + info.AddValue(Names.Factory, Factory); + } + #endregion + + #region ICloneable Members + /// + /// Returns an unconnected copy of the server with the same OpcUrl. + /// + public virtual object Clone() + { + // do a memberwise clone. + var clone = (OpcServer)MemberwiseClone(); + + // place clone in disconnected state. + clone.Server = null; + clone.supportedLocales_ = null; + clone.locale_ = null; + clone.ResourceManager = new ResourceManager("Technosoftware.DaAeHdaClient.Resources.Strings", Assembly.GetExecutingAssembly()); + + // return clone. + return clone; + } + #endregion + + #region IOpcServer Members + /// + /// A short descriptive name for the server. + /// + public virtual string ServerName + { + get => serverName_; + set => serverName_ = value; + } + + /// + /// A short descriptive name for the server assigned by the client. + /// + public virtual string ClientName + { + get => clientName_; + set + { + clientName_ = value; + Server?.SetClientName(value); + } + } + /// + /// The OpcUrl that describes the network location of the server. + /// + public virtual OpcUrl Url + { + get => (OpcUrl)opcUrl_?.Clone(); + set => SetUrl(value); + } + + /// + /// The default of locale used by the remote server. + /// + public virtual string Locale => locale_; + + /// + /// The set of locales supported by the remote server. + /// + public virtual string[] SupportedLocales => (string[])supportedLocales_?.Clone(); + + /// + /// Whether the remote server is currently connected. + /// + public virtual bool IsConnected => (Server != null); + + /// + /// Allows the client to optionally register a client name with the server. This is included primarily for debugging purposes. The recommended behavior is that the client set his Node name and EXE name here. + /// + public virtual void SetClientName(string clientName) + { + ClientName = clientName; + } + + /// + /// Establishes a physical connection to the remote server. + /// + public virtual void Connect() + { + Connect(opcUrl_, null); + } + + /// Establishes a physical connection to the remote server. + /// If an OPC specific error occur this exception is raised. The Result field includes then the OPC specific code. + /// Name of the server. The usual form is http:://xxx/yyy, e.g. http://localhost//TsOpcXSampleServer/Service.asmx. + public virtual void Connect(string url) + { + Factory = null; + var opcUrl = new OpcUrl(url); + Connect(opcUrl, null); + } + + /// + /// Establishes a physical connection to the remote server. + /// + /// Any protocol configuration or user authentication information. + public virtual void Connect(OpcConnectData connectData) + { + Connect(opcUrl_, connectData); + } + + /// + /// Establishes a physical connection to the remote server identified by a OpcUrl. + /// + /// The network address of the remote server. + /// Any protocol configuration or user authentication information. + public virtual void Connect(OpcUrl url, OpcConnectData connectData) + { + if (url == null) throw new ArgumentNullException(nameof(url)); + if (Server != null) throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The server is already connected."); + + // save url. + SetUrl(url); + + try + { + Factory.ForceDa20Usage = ForceDa20Usage; + + // instantiate the server object. + Server = Factory.CreateInstance(url, connectData); + if (Server == null) throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "A connection to the server could not be established."); + + // save the connect data. + connectData_ = connectData; + + try + { + // cache the supported locales. + GetSupportedLocales(); + + // update the default locale. + SetLocale(locale_); + } + catch + { + // ignored + } + } + catch (Exception) + { + if (Server != null) + { + try { Disconnect(); } + catch + { + // ignored + } + } + + throw; + } + } + + /// + /// Disconnects from the server and releases all network resources. + /// + public virtual void Disconnect() + { + if (Server == null) throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The server is not currently connected."); + + // dispose of the remote server object. + Server.Dispose(); + Server = null; + } + + /// + /// Creates a new instance of a server object with the same factory and url. + /// + /// This method does not copy the value of any properties. + /// An unconnected duplicate instance of the server object. + public virtual OpcServer Duplicate() + { + var instance = (OpcServer)Activator.CreateInstance(GetType(), Factory, opcUrl_); + + // preserve the credentials. + instance.connectData_ = connectData_; + + // preserve the locale. + instance.locale_ = locale_; + + return instance; + } + + /// + /// An event to receive server shutdown notifications. + /// + public virtual event OpcServerShutdownEventHandler ServerShutdownEvent + { + add => Server.ServerShutdownEvent += value; + remove => Server.ServerShutdownEvent -= value; + } + + /// + /// The locale used in any error messages or results returned to the client. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + public virtual string GetLocale() + { + if (Server == null) throw new NotConnectedException(); + + // cache the current locale. + locale_ = Server.GetLocale(); + + // return the cached value. + return locale_; + } + + /// + /// Sets the locale used in any error messages or results returned to the client. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + /// A locale that the server supports and is the best match for the requested locale. + public virtual string SetLocale(string locale) + { + if (Server == null) throw new NotConnectedException(); + + try + { + // set the requested locale on the server. + locale_ = Server.SetLocale(locale); + } + catch + { + // find a best match and check if the server supports it. + var revisedLocale = FindBestLocale(locale, supportedLocales_); + + if (revisedLocale != locale) + { + Server.SetLocale(revisedLocale); + } + + // cache the revised locale. + locale_ = revisedLocale; + } + + // return actual local used. + return locale_; + } + + /// + /// Returns the locales supported by the server + /// + /// The first element in the array must be the default locale for the server. + /// An array of locales with the format "[languagecode]-[country/regioncode]". + public virtual string[] GetSupportedLocales() + { + if (Server == null) throw new OpcResultException(new OpcResult(OpcResult.E_FAIL.Code, OpcResult.FuncCallType.SysFuncCall, null), "The server is not currently connected."); + + // cache supported locales. + supportedLocales_ = Server.GetSupportedLocales(); + + // return copy of cached locales. + return SupportedLocales; + } + + /// + /// Returns the localized text for the specified result code. + /// + /// The locale name in the format "[languagecode]-[country/regioncode]". + /// The result code identifier. + /// A message localized for the best match for the requested locale. + public virtual string GetErrorText(string locale, OpcResult resultId) + { + if (Server == null) throw new OpcResultException(OpcResult.E_FAIL, "The server is not currently connected."); + + return Server.GetErrorText(locale ?? locale_, resultId); + } + #endregion + } + + //============================================================================= + // Exceptions + + /// + /// Raised if an operation cannot be executed because the server is not connected. + /// + [Serializable] + public class AlreadyConnectedException : ApplicationException + { + private const string Default = "The remote server is already connected."; + /// + public AlreadyConnectedException() : base(Default) { } + /// + public AlreadyConnectedException(string message) : base(Default + "\r\n" + message) { } + /// + public AlreadyConnectedException(Exception e) : base(Default, e) { } + /// + public AlreadyConnectedException(string message, Exception innerException) : base(Default + "\r\n" + message, innerException) { } + /// + protected AlreadyConnectedException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Raised if an operation cannot be executed because the server is not connected. + /// + [Serializable] + public class NotConnectedException : ApplicationException + { + private const string Default = "The remote server is not currently connected."; + /// + public NotConnectedException() : base(Default) { } + /// + public NotConnectedException(string message) : base(Default + "\r\n" + message) { } + /// + public NotConnectedException(Exception e) : base(Default, e) { } + /// + public NotConnectedException(string message, Exception innerException) : base(Default + "\r\n" + message, innerException) { } + /// + protected NotConnectedException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Raised if an operation cannot be executed because the server is not reachable. + /// + [Serializable] + public class ConnectFailedException : OpcResultException + { + private const string Default = "Could not connect to server."; + /// + public ConnectFailedException() : base(OpcResult.E_ACCESS_DENIED, Default) { } + /// + public ConnectFailedException(string message) : base(OpcResult.E_NETWORK_ERROR, Default + "\r\n" + message) { } + /// + public ConnectFailedException(Exception e) : base(OpcResult.E_NETWORK_ERROR, Default, e) { } + /// + public ConnectFailedException(string message, Exception innerException) : base(OpcResult.E_NETWORK_ERROR, Default + "\r\n" + message, innerException) { } + /// + protected ConnectFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Raised if an operation cannot be executed because the server refuses access. + /// + [Serializable] + public class AccessDeniedException : OpcResultException + { + private const string Default = "The server refused the connection."; + /// + public AccessDeniedException() : base(OpcResult.E_ACCESS_DENIED, Default) { } + /// + public AccessDeniedException(string message) : base(OpcResult.E_ACCESS_DENIED, Default + "\r\n" + message) { } + /// + public AccessDeniedException(Exception e) : base(OpcResult.E_ACCESS_DENIED, Default, e) { } + /// + public AccessDeniedException(string message, Exception innerException) : base(OpcResult.E_NETWORK_ERROR, Default + "\r\n" + message, innerException) { } + /// + protected AccessDeniedException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Raised an remote operation by the server timed out + /// + public class ServerTimeoutException : OpcResultException + { + private const string Default = "The server did not respond within the specified timeout period."; + /// + public ServerTimeoutException() : base(OpcResult.E_TIMEDOUT, Default) { } + /// + public ServerTimeoutException(string message) : base(OpcResult.E_TIMEDOUT, Default + "\r\n" + message) { } + /// + public ServerTimeoutException(Exception e) : base(OpcResult.E_TIMEDOUT, Default, e) { } + /// + public ServerTimeoutException(string message, Exception innerException) : base(OpcResult.E_TIMEDOUT, Default + "\r\n" + message, innerException) { } + /// + protected ServerTimeoutException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Raised an remote operation by the server returned unusable or invalid results. + /// + [Serializable] + public class InvalidResponseException : ApplicationException + { + private const string Default = "The response from the server was invalid or incomplete."; + /// + public InvalidResponseException() : base(Default) { } + /// + public InvalidResponseException(string message) : base(Default + "\r\n" + message) { } + /// + public InvalidResponseException(Exception e) : base(Default, e) { } + /// + public InvalidResponseException(string message, Exception innerException) : base(Default + "\r\n" + message, innerException) { } + /// + protected InvalidResponseException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Raised if the browse position is not valid. + /// + [Serializable] + public class BrowseCannotContinueException : ApplicationException + { + private const string Default = "The browse operation cannot continue."; + /// + public BrowseCannotContinueException() : base(Default) { } + /// + public BrowseCannotContinueException(string message) : base(Default + "\r\n" + message) { } + /// + public BrowseCannotContinueException(string message, Exception innerException) : base(Default + "\r\n" + message, innerException) { } + /// + protected BrowseCannotContinueException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Raised if the browse position is not valid. + /// + [Serializable] + public class BadInternalErrorException : ApplicationException + { + private const string Default = "License required! You can't use this feature."; + /// + public BadInternalErrorException() : base(Default) { } + /// + public BadInternalErrorException(string message) : base(message) { } + /// + public BadInternalErrorException(string message, Exception innerException) : base(message, innerException) { } + /// + protected BadInternalErrorException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// Exception that is raise when a DCOM call is cancelled due to timeout + /// + /// + [Serializable] + public class DCOMCallCancelledException : ApplicationException + { + private const string Default = "The current pending DCOM call was cancelled"; + /// + public DCOMCallCancelledException() : base(Default) { } + /// + public DCOMCallCancelledException(string message) : base(message) { } + /// + public DCOMCallCancelledException(string message, Exception innerException) : base(message, innerException) { } + /// + protected DCOMCallCancelledException(SerializationInfo info, StreamingContext context) : base(info, context) { } + + } + +} diff --git a/Technosoftware/DaAeHdaClient/OpcServerDescription.cs b/Technosoftware/DaAeHdaClient/OpcServerDescription.cs new file mode 100644 index 0000000..e6bf0b7 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcServerDescription.cs @@ -0,0 +1,85 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Information about an OPC Server + /// + public class OpcServerDescription + { + /// + /// The server types supported by this server. Standard types are defined + /// by the ServerType class. + /// + public uint ServerTypes { get; set; } + + /// + /// Name of the server software vendor. + /// + public string VendorName { get; set; } + + /// + /// Namespace for types defined by this vendor. This may or + /// may not be the same as the VendorName. Null or empty if not used. + /// + public string VendorNamespace { get; set; } + + /// + /// Name of the server software. + /// + public string ServerName { get; set; } + + /// + /// Namespace for server-specific types. Null or empty if not used. + /// This name is typically a concatentation of the VendorNamespace + /// and the ServerName (separated by a '/' character) + /// (e.g "MyVendorNamespace/MyServer"). + /// + public string ServerNamespace { get; set; } + + /// + /// The HostName of the machine in which the server resides (runs). The + /// HostName is used as part of the object path in InstanceIds of the + /// server's objects. + /// + public string HostName { get; set; } + + /// + /// The name of the system that contains the objects accessible + /// through the server. Null or empty if the server provides access + /// to objects from more than one system. + /// + public string SystemName { get; set; } + + /// + /// Detailed information about the server. + /// Set to null if the ServerDescription is being + /// accessed without a client context. + /// + public OpcServerDetail ServerDetails { get; set; } + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcServerDetail.cs b/Technosoftware/DaAeHdaClient/OpcServerDetail.cs new file mode 100644 index 0000000..975a985 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcServerDetail.cs @@ -0,0 +1,55 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Detailed information about the server. + /// Set to null if the ServerDescription is being accessed without a client context. + /// + public class OpcServerDetail + { + /// + /// The time the server was last started. + /// + public DateTime StartTime; + + /// + /// The build number of the server. + /// + public string BuildNumber; + + /// + /// The version of the server. + /// + public string Version; + + /// + /// Vendor-specific information about the server. + /// + public string VendorInfo; + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcServerState.cs b/Technosoftware/DaAeHdaClient/OpcServerState.cs new file mode 100644 index 0000000..65d501b --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcServerState.cs @@ -0,0 +1,84 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// The set of possible server states. + /// + public enum OpcServerState + { + /// + /// The server state is not known. + /// + Unknown, + + /// + /// The server is running normally. This is the usual state for a server + /// + Operational, + + /// + /// The server is not operational due to a fault. The server is no longer functioning. The recovery procedure from this situation is vendor specific. An error code of E_FAIL should generally be returned from any other server method. + /// + Faulted, + + /// + /// The server is running but has no configuration information loaded and thus cannot function normally. Note this state implies that the server needs configuration information in order to function. Servers which do not require configuration information should not return this state. + /// + NeedsConfiguration, + + /// + /// The server has been temporarily suspended via some vendor specific method and is not getting or sending data. Note that Quality will be returned as OPC_QUALITY_OUT_OF_SERVICE. + /// + OutOfService, + + /// + /// The server is in Diagnostics Mode. The outputs are disconnected from the real hardware but the server will otherwise behave normally. Inputs may be real or may be simulated depending on the vendor implementation. Quality will generally be returned normally. + /// + Diagnostics, + + /// + /// The server is not operational. The outputs are disconnected from the real hardware but the server will otherwise behave normally. Inputs may be real or may be simulated depending on the vendor implementation. Quality will generally be returned normally. + /// + NotConnected, + + /// + /// The server is not operational because it is starting up. + /// + Initializing, + + /// + /// The server is operational but it is shutting down and aborting all of its client contexts. + /// + Aborting, + + /// + /// The server is not operational, but the reason is not known. + /// + NotOperational, + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcServerStatus.cs b/Technosoftware/DaAeHdaClient/OpcServerStatus.cs new file mode 100644 index 0000000..907eef1 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcServerStatus.cs @@ -0,0 +1,173 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Contains properties that describe the current status of an OPC server. + /// + [Serializable] + public class OpcServerStatus : ICloneable + { + #region Fields + private OpcServerState serverState_ = OpcServerState.Unknown; + private DateTime startTime_ = DateTime.MinValue; + private DateTime currentTime_ = DateTime.MinValue; + private DateTime lastUpdateTime_ = DateTime.MinValue; + private int bandWidth_ = -1; + private short majorVersion_; + private short minorVersion_; + private short buildNumber_; + #endregion + + #region Properties + /// + /// The vendor name and product name for the server. + /// + public string VendorInfo { get; set; } + + /// + /// A string that contains the server software version number. + /// + public string ProductVersion { get; set; } + + /// + /// The server for which the status is being reported. + /// The ServerType enumeration is used to identify + /// the server. If the enumeration indicates multiple + /// server types, then this is the status of the entire + /// server. For example, if the server wraps an + /// OPC DA and OPC AE server, then if this ServerType + /// indicates both, the status is for the entire server, and + /// not for an individual wrapped server. + /// + public uint ServerType { get; set; } + + /// + /// The current state of the server. + /// + public OpcServerState ServerState + { + get => serverState_; + set => serverState_ = value; + } + + /// + /// A string that describes the current server state. + /// + public string StatusInfo { get; set; } + + /// + /// The time when the server started. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime StartTime + { + get => startTime_; + set => startTime_ = value; + } + + /// + /// Th current time at the server. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime CurrentTime + { + get => currentTime_; + set => currentTime_ = value; + } + + /// + /// The maximum number of values that can be returned by the server on a per item basis. + /// + public int MaxReturnValues { get; set; } + + /// + /// The last time the server sent an data update to the client. + /// The ApplicationInstance.TimeAsUtc property defines + /// the time format (UTC or local time). + /// + public DateTime LastUpdateTime + { + get => lastUpdateTime_; + set => lastUpdateTime_ = value; + } + + /// + /// Total number of groups being managed by the server. + /// + public int GroupCount { get; set; } + + /// + /// The behavior of of this value is server specific. + /// + public int BandWidth + { + get => bandWidth_; + set => bandWidth_ = value; + } + + /// + /// The major version of the used server issue. + /// + public short MajorVersion + { + get => majorVersion_; + set => majorVersion_ = value; + } + + /// + /// The minor version of the used server issue. + /// + public short MinorVersion + { + get => minorVersion_; + set => minorVersion_ = value; + } + + /// + /// The build number of the used server issue. + /// + public short BuildNumber + { + get => buildNumber_; + set => buildNumber_ = value; + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep-copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcSpecification.cs b/Technosoftware/DaAeHdaClient/OpcSpecification.cs new file mode 100644 index 0000000..f119243 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcSpecification.cs @@ -0,0 +1,137 @@ +#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; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A description of an interface version defined by an OPC specification. + /// + [Serializable] + public struct OpcSpecification + { + #region Constants + /// OPC Alarms&Events 1.0 and OPC ALarms&Events 1.1. + public static readonly OpcSpecification OPC_AE_10 = new OpcSpecification("58E13251-AC87-11d1-84D5-00608CB8A7E9", "Alarms and Event 1.XX"); + + /// OPC Data Access 1.0. + public static readonly OpcSpecification OPC_DA_10 = new OpcSpecification("63D5F430-CFE4-11d1-B2C8-0060083BA1FB", "Data Access 1.0a"); + /// OPC Data Access 2.0. + public static readonly OpcSpecification OPC_DA_20 = new OpcSpecification("63D5F432-CFE4-11d1-B2C8-0060083BA1FB", "Data Access 2.XX"); + /// OPC Data Access 3.0. + public static readonly OpcSpecification OPC_DA_30 = new OpcSpecification("CC603642-66D7-48f1-B69A-B625E73652D7", "Data Access 3.00"); + /// OPC XML-DA 1.0. + public static readonly OpcSpecification XML_DA_10 = new OpcSpecification("3098EDA4-A006-48b2-A27F-247453959408", "XML Data Access 1.00"); + + /// OPC Historical Data Access 1.0. + public static readonly OpcSpecification OPC_HDA_10 = new OpcSpecification("7DE5B060-E089-11d2-A5E6-000086339399", "Historical Data Access 1.XX"); + #endregion + + #region Fields + private string id_; + private string description_; + #endregion + + #region Constructors, Destructor, Initialization + /// + /// Initializes the object with the description and a GUID as a string. + /// + public OpcSpecification(string id, string description) + { + id_ = id; + description_ = description; + } + #endregion + + #region Properties + /// + /// The unique identifier for the interface version. + /// + public string Id + { + get => id_; + set => id_ = value; + } + + /// + /// The human readable description for the interface version. + /// + public string Description + { + get => description_; + set => description_ = value; + } + #endregion + + #region Public Methods + /// + /// Returns true if the objects are equal. + /// + public static bool operator ==(OpcSpecification a, OpcSpecification b) + { + return a.Equals(b); + } + + /// + /// Returns true if the objects are not equal. + /// + public static bool operator !=(OpcSpecification a, OpcSpecification b) + { + return !a.Equals(b); + } + #endregion + + #region Object Member Overrides + /// + /// Determines if the object is equal to the specified value. + /// + public override bool Equals(object target) + { + if (target != null && (target is OpcSpecification)) + { + return (Id == ((OpcSpecification)target).Id); + } + + return false; + } + + /// + /// Converts the object to a string used for display. + /// + public override string ToString() + { + return Description; + } + + /// + /// Returns a suitable hash code for the result. + /// + public override int GetHashCode() + { + return (Id != null) ? Id.GetHashCode() : base.GetHashCode(); + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcType.cs b/Technosoftware/DaAeHdaClient/OpcType.cs new file mode 100644 index 0000000..6ea979d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcType.cs @@ -0,0 +1,114 @@ +#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; +using System.Reflection; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Defines constants for standard data types. + /// + public class OpcType + { + /// + public static Type SBYTE = typeof(sbyte); + /// + public static Type BYTE = typeof(byte); + /// + public static Type SHORT = typeof(short); + /// + public static Type USHORT = typeof(ushort); + /// + public static Type INT = typeof(int); + /// + public static Type UINT = typeof(uint); + /// + public static Type LONG = typeof(long); + /// + public static Type ULONG = typeof(ulong); + /// + public static Type FLOAT = typeof(float); + /// + public static Type DOUBLE = typeof(double); + /// + public static Type DECIMAL = typeof(decimal); + /// + public static Type BOOLEAN = typeof(bool); + /// + public static Type DATETIME = typeof(DateTime); + /// + public static Type DURATION = typeof(TimeSpan); + /// + public static Type STRING = typeof(string); + /// + public static Type ANY_TYPE = typeof(object); + /// + public static Type BINARY = typeof(byte[]); + /// + public static Type ARRAY_SHORT = typeof(short[]); + /// + public static Type ARRAY_USHORT = typeof(ushort[]); + /// + public static Type ARRAY_INT = typeof(int[]); + /// + public static Type ARRAY_UINT = typeof(uint[]); + /// + public static Type ARRAY_LONG = typeof(long[]); + /// + public static Type ARRAY_ULONG = typeof(ulong[]); + /// + public static Type ARRAY_FLOAT = typeof(float[]); + /// + public static Type ARRAY_DOUBLE = typeof(double[]); + /// + public static Type ARRAY_DECIMAL = typeof(decimal[]); + /// + public static Type ARRAY_BOOLEAN = typeof(bool[]); + /// + public static Type ARRAY_DATETIME = typeof(DateTime[]); + /// + public static Type ARRAY_STRING = typeof(string[]); + /// + public static Type ARRAY_ANY_TYPE = typeof(object[]); + /// + public static Type ILLEGAL_TYPE = typeof(OpcType); + + + /// + /// Returns an array of all well-known types. + /// + public static Type[] Enumerate() + { + var values = new ArrayList(); + + var fields = typeof(OpcType).GetFields(BindingFlags.Static | BindingFlags.Public); + + Array.ForEach(fields, field => values.Add(field.GetValue(typeof(Type)))); + + return (Type[])values.ToArray(typeof(Type)); + } + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcUrl.cs b/Technosoftware/DaAeHdaClient/OpcUrl.cs new file mode 100644 index 0000000..24f8fd7 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcUrl.cs @@ -0,0 +1,318 @@ +#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.Net; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Contains information required to connect to the OPC server. + /// + [Serializable] + public class OpcUrl : ICloneable + { + #region Constructors, Destructor, Initialization + /// + /// Initializes an empty instance. + /// + public OpcUrl() + { + Scheme = OpcUrlScheme.HTTP; + HostName = "localhost"; + Port = 0; + Path = null; + } + + /// + /// Initializes an instance by providing OPC specification, OPC URL scheme and an URL string. + /// + /// A description of an interface version defined by an OPC specification. + /// The scheme (protocol) for the URL + /// The URL of the OPC server. + public OpcUrl(OpcSpecification specification, string scheme, string url) + { + Specification = specification; + HostName = "localhost"; + Port = 0; + Path = null; + + ParseUrl(url); + + Scheme = scheme; + } + + /// + /// Initializes an instance by parsing an URL string. + /// + /// The URL of the OPC server. + public OpcUrl(string url) + { + Scheme = OpcUrlScheme.HTTP; + HostName = "localhost"; + Port = 0; + Path = null; + + ParseUrl(url); + } + #endregion + + #region Properties + /// + /// The supported OPC specification. + /// + public OpcSpecification Specification { get; set; } + + /// + /// The scheme (protocol) for the URL + /// + public string Scheme { get; set; } + + /// + /// The host name for the URL. + /// + public string HostName { get; set; } + + /// + /// The port name for the URL (0 means default for protocol). + /// + public int Port { get; set; } + + /// + /// The path for the URL. + /// + public string Path { get; set; } + #endregion + + #region Object Method Overrides + /// + /// Returns a URL string for the object. + /// + public override string ToString() + { + var hostName = string.IsNullOrEmpty(HostName) ? "localhost" : HostName; + + if (Port > 0) + { + return $"{Scheme}://{hostName}:{Port}/{Path}"; + } + else + { + return $"{Scheme}://{hostName}/{Path}"; + } + } + + /// + /// Compares the object to either another URL object or a URL string. + /// + public override bool Equals(object target) + { + OpcUrl url; + + if (target != null && target.GetType() == typeof(OpcUrl)) + { + url = (OpcUrl)target; + } + else + { + url = null; + } + + if (target != null && target.GetType() == typeof(string)) + { + url = new OpcUrl((string)target); + } + + if (url == null) return false; + if (url.Path != Path) return false; + if (url.Scheme != Scheme) return false; + if (url.HostName != HostName) return false; + if (url.Port != Port) return false; + + return true; + } + + /// + /// Returns a hash code for the object. + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + #endregion + + #region ICloneable Members + /// + /// Returns a deep copy of the object. + /// + public virtual object Clone() + { + return MemberwiseClone(); + } + #endregion + + #region Private Methods + private void ParseUrl(string url) + { + + var buffer = url; + + // extract the scheme (default is http). + var index = buffer.IndexOf("://", StringComparison.Ordinal); + + if (index >= 0) + { + Scheme = buffer.Substring(0, index); + buffer = buffer.Substring(index + 3); + } + + index = buffer.IndexOfAny(new[] { '/' }); + + if (index < 0) + { + Path = buffer; + return; + } + + var hostPortString = buffer.Substring(0, index); + + IPAddress.TryParse(hostPortString, out var address); + + if (address != null && address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) + { + if (hostPortString.Contains("]")) + { + HostName = hostPortString.Substring(0, hostPortString.IndexOf("]", StringComparison.Ordinal) + 1); + if (hostPortString.Substring(hostPortString.IndexOf(']')).Contains(":")) + { + var portString = hostPortString.Substring(hostPortString.LastIndexOf(':') + 1); + if (portString != "") + { + try + { + Port = Convert.ToUInt16(portString); + } + catch + { + Port = 0; + } + } + else + { + Port = 0; + } + } + else + { + Port = 0; + } + + Path = buffer.Substring(index + 1); + } + else + { + HostName = $"[{hostPortString}]"; + Port = 0; + } + + Path = buffer.Substring(index + 1); + } + else + { + + // extract the hostname (default is localhost). + index = buffer.IndexOfAny(new[] { ':', '/' }); + + if (index < 0) + { + Path = buffer; + return; + } + + HostName = buffer.Substring(0, index); + + // extract the port number (default is 0). + if (buffer[index] == ':') + { + buffer = buffer.Substring(index + 1); + index = buffer.IndexOf("/", StringComparison.Ordinal); + + string port; + + if (index >= 0) + { + port = buffer.Substring(0, index); + buffer = buffer.Substring(index + 1); + } + else + { + port = buffer; + buffer = ""; + } + + try + { + Port = Convert.ToUInt16(port); + } + catch + { + Port = 0; + } + } + else + { + buffer = buffer.Substring(index + 1); + } + + // extract the path. + Path = buffer; + + // In case the specification is not set, we try to find it out based on the Scheme + if (Specification.Id == null) + { + if (Scheme == OpcUrlScheme.DA) + { + Specification = OpcSpecification.OPC_DA_20; + return; + } + if (Scheme == OpcUrlScheme.AE) + { + Specification = OpcSpecification.OPC_AE_10; + return; + } + if (Scheme == OpcUrlScheme.HDA) + { + Specification = OpcSpecification.OPC_HDA_10; + return; + } + if (Scheme == OpcUrlScheme.HTTP) + { + Specification = OpcSpecification.XML_DA_10; + } + } + } + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcUrlScheme.cs b/Technosoftware/DaAeHdaClient/OpcUrlScheme.cs new file mode 100644 index 0000000..2bc2b31 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcUrlScheme.cs @@ -0,0 +1,60 @@ +#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 + +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// Defines string constants for well-known OPC URL schemes. + /// + public class OpcUrlScheme + { + /// + /// OPC over http. + /// + public const string HTTP = "http"; + + /// + /// OPC Alarms and Events + /// + public const string AE = "opcae"; + + /// + /// OPC Data Access + /// + public const string DA = "opcda"; + + /// + /// OPC Historical Data Access + /// + public const string HDA = "opchda"; + + /// + /// OPC Express Interface + /// + public const string XI = "opcxi"; + + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcUserIdentity.cs b/Technosoftware/DaAeHdaClient/OpcUserIdentity.cs new file mode 100644 index 0000000..025ff4c --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcUserIdentity.cs @@ -0,0 +1,403 @@ +#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.Text; +using System.Security.Cryptography; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// The user identity to use when connecting to the OPC server. + /// + public class OpcUserIdentity + { + /////////////////////////////////////////////////////////////////////// + #region Fields + + private RSAParameters _rsaParams; + + private readonly byte[] _domainName; + private readonly bool _domainNameValid; + private readonly bool _usernameValid; + private readonly byte[] _username; + private readonly bool _passwordValid; + private readonly byte[] _password; + private readonly bool _clientCertificateNameValid; + private readonly byte[] _clientCertificateName; + private readonly bool _serverCertificateNameValid; + private readonly byte[] _serverCertificateName; + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Constructors, Destructor, Initialization + + /// + /// Sets the username and password (extracts the domain from the username if a '\' is present). + /// + /// The user name + /// The password + public OpcUserIdentity(string username, string password) + { + var ByteConverter = new UnicodeEncoding(); + using (var RSA = new RSACryptoServiceProvider()) + { + string domainName = null; + _rsaParams = RSA.ExportParameters(true); + RSA.ImportParameters(_rsaParams); + if (!string.IsNullOrEmpty(username)) + { + var index = username.IndexOf('\\'); + + if (index != -1) + { + domainName = username.Substring(0, index); + username = username.Substring(index + 1); + } + _usernameValid = true; + var userIdBytes = ByteConverter.GetBytes(username); + _username = RSA.Encrypt(userIdBytes, false); + } + + if (!string.IsNullOrEmpty(password)) + { + _passwordValid = true; + var userKeyBytes = ByteConverter.GetBytes(password); + _password = RSA.Encrypt(userKeyBytes, false); + } + + if (!string.IsNullOrEmpty(domainName)) + { + _domainNameValid = true; + var domainBytes = ByteConverter.GetBytes(domainName); + _domainName = RSA.Encrypt(domainBytes, false); + } + + } + } + + /// + /// Sets the username and password. + /// + /// The windows domain name + /// The user name + /// The password + public OpcUserIdentity(string domainName, string userName, string password) + : this(userName, password) + { + var ByteConverter = new UnicodeEncoding(); + using (var RSA = new RSACryptoServiceProvider()) + { + RSA.ImportParameters(_rsaParams); + if (!string.IsNullOrEmpty(domainName)) + { + _domainNameValid = true; + var domainBytes = ByteConverter.GetBytes(domainName); + _domainName = RSA.Encrypt(domainBytes, false); + } + } + } + + /// + /// Sets the username and password. + /// + /// The windows domain name + /// The user name + /// The password + /// The Client Certificate name + /// The Server Certificate name + public OpcUserIdentity(string domainName, string userName, string password, string clientCertificateName, string serverCertificateName) + : this(domainName, userName, password) + { + var ByteConverter = new UnicodeEncoding(); + using (var RSA = new RSACryptoServiceProvider()) + { + RSA.ImportParameters(_rsaParams); + if (!string.IsNullOrEmpty(clientCertificateName)) + { + _clientCertificateNameValid = true; + var clientCertificateBytes = ByteConverter.GetBytes(clientCertificateName); + _clientCertificateName = RSA.Encrypt(clientCertificateBytes, false); + } + if (!string.IsNullOrEmpty(serverCertificateName)) + { + _serverCertificateNameValid = true; + var serverCertificateBytes = ByteConverter.GetBytes(serverCertificateName); + _serverCertificateName = RSA.Encrypt(serverCertificateBytes, false); + } + } + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Properties + + /// + /// The windows domain name. + /// + public string Domain + { + get + { + if (_domainNameValid) + { + try + { + using (var RSA = new RSACryptoServiceProvider()) + { + RSA.ImportParameters(_rsaParams); + var domainNameBytes = RSA.Decrypt(_domainName, false); + var ByteConverter = new UnicodeEncoding(); + return ByteConverter.GetString(domainNameBytes); + } + } + catch (Exception) + { + throw new OpcResultException(OpcResult.E_FAIL, "The user info object has been corrupted."); + } + } + return null; + } + } + + /// + /// The user name. + /// + public string Username + { + get + { + if (_usernameValid) + { + try + { + using (var RSA = new RSACryptoServiceProvider()) + { + RSA.ImportParameters(_rsaParams); + var userNameBytes = RSA.Decrypt(_username, false); + var ByteConverter = new UnicodeEncoding(); + return ByteConverter.GetString(userNameBytes); + } + } + catch (Exception) + { + throw new OpcResultException(OpcResult.E_FAIL, "The user info object has been corrupted."); + } + } + return null; + } + } + + /// + /// The password. + /// + public string Password + { + get + { + if (_passwordValid) + { + try + { + using (var RSA = new RSACryptoServiceProvider()) + { + RSA.ImportParameters(_rsaParams); + var passwordBytes = RSA.Decrypt(_password, false); + var ByteConverter = new UnicodeEncoding(); + return ByteConverter.GetString(passwordBytes); + } + } + catch (Exception) + { + throw new OpcResultException(OpcResult.E_FAIL, "The user info object has been corrupted."); + } + } + return null; + } + } + + /// + /// Gets or sets a license key to use when activating the server. + /// + public string LicenseKey { get; set; } + + /// + /// The Client Certificate name for certificate mode authentication of the server access. + /// + public string ClientCertificateName + { + get + { + if (_clientCertificateNameValid) + { + try + { + using (var RSA = new RSACryptoServiceProvider()) + { + RSA.ImportParameters(_rsaParams); + var clientCertificateBytes = RSA.Decrypt(_clientCertificateName, false); + var ByteConverter = new UnicodeEncoding(); + return ByteConverter.GetString(clientCertificateBytes); + } + } + catch (Exception) + { + throw new OpcResultException(OpcResult.E_FAIL, "The user info object has been corrupted."); + } + } + return null; + } + } + + /// + /// The Server Certificate name for certificate mode authentication of the server access. + /// + public string ServerCertificateName + { + get + { + if (_serverCertificateNameValid) + { + try + { + using (var RSA = new RSACryptoServiceProvider()) + { + RSA.ImportParameters(_rsaParams); + var serverCertificateBytes = RSA.Decrypt(_serverCertificateName, false); + var ByteConverter = new UnicodeEncoding(); + return ByteConverter.GetString(serverCertificateBytes); + } + } + catch (Exception) + { + throw new OpcResultException(OpcResult.E_FAIL, "The user info object has been corrupted."); + } + } + return null; + } + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Public Methods + + /// + /// Whether the identity represents an the default identity. + /// + public static bool IsDefault(OpcUserIdentity identity) + { + if (identity != null) + { + return (!identity._usernameValid); + } + + return true; + } + + #endregion + + /////////////////////////////////////////////////////////////////////// + #region Public Methods (Comparison Operators) + + /// + /// Determines if the object is equal to the specified value. + /// + /// The OpcUserIdentity to compare with + /// True if the objects are equal; otherwise false. + public override bool Equals(object target) + { + object identity = target as OpcUserIdentity; + + if (identity == null) + { + return false; + } + + return false; + } + + /// + /// Converts the object to a string used for display. + /// + public override string ToString() + { + if (_domainNameValid) + { + return string.Format("{0}\\{1}", Domain, Username); + } + + return Username; + } + + /// + /// Returns a suitable hash code for the result. + /// + public override int GetHashCode() + { + if (_usernameValid) + { + return Username.GetHashCode(); + } + + return 0; + } + + /// + /// Returns true if the objects are equal. + /// + /// The first OpcUserIdentity to compare. + /// The second OpcUserIdentity to compare. + /// True if the objects are equal; otherwise false. + public static bool operator ==(OpcUserIdentity a, OpcUserIdentity b) + { + if (ReferenceEquals(a, null)) + { + return ReferenceEquals(b, null); + } + + return a.Equals(b); + } + + /// + /// Returns true if the objects are not equal. + /// + /// The first OpcUserIdentity to compare. + /// The second OpcUserIdentity to compare. + /// True if the objects are not equal; otherwise false. + public static bool operator !=(OpcUserIdentity a, OpcUserIdentity b) + { + if (ReferenceEquals(a, null)) + { + return !ReferenceEquals(b, null); + } + + return !a.Equals(b); + } + + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcWriteableCollection.cs b/Technosoftware/DaAeHdaClient/OpcWriteableCollection.cs new file mode 100644 index 0000000..7f72eca --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcWriteableCollection.cs @@ -0,0 +1,361 @@ +#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; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A writeable collection class which can be used to expose arrays as properties of classes. + /// + [Serializable] + public class OpcWriteableCollection : ICollection, IList, ICloneable, ISerializable + { + #region Fields + private ArrayList _array; + private Type _elementType; + #endregion + + #region Public Interface + /// + /// An indexer for the collection. + /// + public virtual object this[int index] + { + get => _array[index]; + set => _array[index] = value; + } + + /// + /// Returns a copy of the collection as an array. + /// + public virtual Array ToArray() + { + return _array.ToArray(_elementType); + } + + /// + /// Adds a list of values to the collection. + /// + public virtual void AddRange(ICollection collection) + { + if (collection != null) + { + foreach (var element in collection) + { + ValidateElement(element); + } + + _array.AddRange(collection); + } + } + #endregion + + #region Protected Interface + /// + /// Creates a collection that wraps the specified array instance. + /// + protected OpcWriteableCollection(ICollection array, Type elementType) + { + // copy array. + if (array != null) + { + _array = new ArrayList(array); + } + else + { + _array = new ArrayList(); + } + + // set default element type. + _elementType = typeof(object); + + // verify that current contents of the array are the correct type. + if (elementType != null) + { + foreach (var element in _array) + { + ValidateElement(element); + } + + _elementType = elementType; + } + } + + /// + /// The array instance exposed by the collection. + /// + protected virtual ArrayList Array + { + get => _array; + + set + { + _array = value; + + if (_array == null) + { + _array = new ArrayList(); + } + } + } + + /// + /// The type of objects allowed in the collection. + /// + protected virtual Type ElementType + { + get => _elementType; + + set + { + // verify that current contents of the array are the correct type. + foreach (var element in _array) + { + ValidateElement(element); + } + + _elementType = value; + } + } + + /// + /// Throws an exception if the element is not valid for the collection. + /// + protected virtual void ValidateElement(object element) + { + if (element == null) + { + throw new ArgumentException(string.Format(INVALID_VALUE, element)); + } + + if (!_elementType.IsInstanceOfType(element)) + { + throw new ArgumentException(string.Format(INVALID_TYPE, element.GetType())); + } + } + + /// + protected const string INVALID_VALUE = "The value '{0}' cannot be added to the collection."; + /// + protected const string INVALID_TYPE = "A value with type '{0}' cannot be added to the collection."; + #endregion + + #region ISerializable Members + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string COUNT = "CT"; + internal const string ELEMENT = "EL"; + internal const string ELEMENT_TYPE = "ET"; + } + + /// + /// Contructs a server by de-serializing its OpcUrl from the stream. + /// + protected OpcWriteableCollection(SerializationInfo info, StreamingContext context) + { + _elementType = (Type)info.GetValue(Names.ELEMENT_TYPE, typeof(Type)); + + var count = (int)info.GetValue(Names.COUNT, typeof(int)); + + _array = new ArrayList(count); + + for (var ii = 0; ii < count; ii++) + { + _array.Add(info.GetValue(Names.ELEMENT + ii.ToString(), typeof(object))); + } + } + + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.ELEMENT_TYPE, _elementType); + info.AddValue(Names.COUNT, _array.Count); + + for (var ii = 0; ii < _array.Count; ii++) + { + info.AddValue(Names.ELEMENT + ii.ToString(), _array[ii]); + } + } + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public virtual int Count => _array.Count; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public virtual void CopyTo(Array array, int index) + { + if (_array != null) + { + _array.CopyTo(array, index); + } + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual object SyncRoot => this; + + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return _array.GetEnumerator(); + } + #endregion + + #region IList Members + /// + /// Gets a value indicating whether the collection is read-only. + /// + public virtual bool IsReadOnly => false; + + /// + /// Gets or sets the element at the specified index. + /// + object IList.this[int index] + { + get => this[index]; + set => this[index] = value; + } + + /// + /// Removes the IList item at the specified index. + /// + /// The zero-based index of the item to remove. + public virtual void RemoveAt(int index) + { + _array.RemoveAt(index); + } + + /// + /// Inserts an item to the IList at the specified position. + /// + /// The zero-based index at which value should be inserted. + /// The Object to insert into the IList. + public virtual void Insert(int index, object value) + { + ValidateElement(value); + _array.Insert(index, value); + } + + /// + /// Removes the first occurrence of a specific object from the IList. + /// + /// The Object to remove from the IList. + public virtual void Remove(object value) + { + _array.Remove(value); + } + + /// + /// Determines whether the IList contains a specific value. + /// + /// The Object to locate in the IList. + /// true if the Object is found in the IList; otherwise, false. + public virtual bool Contains(object value) + { + return _array.Contains(value); + } + + /// + /// Removes all items from the IList. + /// + public virtual void Clear() + { + _array.Clear(); + } + + /// + /// Determines the index of a specific item in the IList. + /// + /// The Object to locate in the IList. + /// The index of value if found in the list; otherwise, -1. + public virtual int IndexOf(object value) + { + return _array.IndexOf(value); + } + + /// + /// Adds an item to the IList. + /// + /// The Object to add to the IList. + /// The position into which the new element was inserted. + public virtual int Add(object value) + { + ValidateElement(value); + return _array.Add(value); + } + + /// + /// Indicates whether the IList has a fixed size. + /// + public virtual bool IsFixedSize => false; + + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the collection. + /// + public virtual object Clone() + { + var clone = (OpcWriteableCollection)MemberwiseClone(); + + clone._array = new ArrayList(); + + for (var ii = 0; ii < _array.Count; ii++) + { + clone.Add(OpcConvert.Clone(_array[ii])); + } + + return clone; + } + #endregion + } +} diff --git a/Technosoftware/DaAeHdaClient/OpcWriteableDictionary.cs b/Technosoftware/DaAeHdaClient/OpcWriteableDictionary.cs new file mode 100644 index 0000000..1b562df --- /dev/null +++ b/Technosoftware/DaAeHdaClient/OpcWriteableDictionary.cs @@ -0,0 +1,375 @@ +#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; +using System.Runtime.Serialization; +#endregion + +namespace Technosoftware.DaAeHdaClient +{ + /// + /// A read only dictionary class which can be used to expose arrays as properties of classes. + /// + [Serializable] + public class OpcWriteableDictionary : IDictionary, ISerializable + { + #region Fields + private Hashtable dictionary_ = new Hashtable(); + private Type keyType_; + private Type valueType_; + #endregion + + #region Protected Interface + /// + /// Creates a collection that wraps the specified array instance. + /// + protected OpcWriteableDictionary(IDictionary dictionary, Type keyType, Type valueType) + { + // set default key/value types. + keyType_ = keyType ?? typeof(object); + valueType_ = valueType ?? typeof(object); + + // copy dictionary. + Dictionary = dictionary; + } + + /// + /// The dictionary instance exposed by the collection. + /// + protected virtual IDictionary Dictionary + { + get => dictionary_; + + set + { + // copy dictionary. + if (value != null) + { + // verify that current keys of the dictionary are the correct type. + if (keyType_ != null) + { + foreach (var element in value.Keys) + { + ValidateKey(element, keyType_); + } + } + + // verify that current values of the dictionary are the correct type. + if (valueType_ != null) + { + foreach (var element in value.Values) + { + ValidateValue(element, valueType_); + } + } + + dictionary_ = new Hashtable(value); + } + else + { + dictionary_ = new Hashtable(); + } + } + } + + /// + /// The type of objects allowed as keys in the dictionary. + /// + // ReSharper disable once UnusedMember.Global + protected Type KeyType + { + get => keyType_; + + set + { + // verify that current keys of the dictionary are the correct type. + foreach (var element in dictionary_.Keys) + { + ValidateKey(element, value); + } + + keyType_ = value; + } + } + + /// + /// The type of objects allowed as values in the dictionary. + /// + protected Type ValueType + { + get => valueType_; + + set + { + // verify that current values of the dictionary are the correct type. + foreach (var element in dictionary_.Values) + { + ValidateValue(element, value); + } + + valueType_ = value; + } + } + + /// + /// Throws an exception if the key is not valid for the dictionary. + /// + protected virtual void ValidateKey(object element, Type type) + { + if (element == null) + { + throw new ArgumentException(string.Format(INVALID_VALUE, null, "key")); + } + + if (!type.IsInstanceOfType(element)) + { + throw new ArgumentException(string.Format(INVALID_TYPE, element.GetType(), "key")); + } + } + + /// + /// Throws an exception if the value is not valid for the dictionary. + /// + protected virtual void ValidateValue(object element, Type type) + { + if (element != null) + { + if (!type.IsInstanceOfType(element)) + { + throw new ArgumentException(string.Format(INVALID_TYPE, element.GetType(), "value")); + } + } + } + + /// + protected const string INVALID_VALUE = "The {1} '{0}' cannot be added to the dictionary."; + /// + protected const string INVALID_TYPE = "A {1} with type '{0}' cannot be added to the dictionary."; + #endregion + + #region ISerializable Members + /// + /// A set of names for fields used in serialization. + /// + private class Names + { + internal const string Count = "CT"; + internal const string Key = "KY"; + internal const string Value = "VA"; + internal const string KeyType = "KT"; + internal const string ValueValue = "VT"; + } + + /// + /// Construct a server by de-serializing its OpcUrl from the stream. + /// + protected OpcWriteableDictionary(SerializationInfo info, StreamingContext context) + { + keyType_ = (Type)info.GetValue(Names.KeyType, typeof(Type)); + valueType_ = (Type)info.GetValue(Names.ValueValue, typeof(Type)); + + var count = (int)info.GetValue(Names.Count, typeof(int)); + + dictionary_ = new Hashtable(); + + for (var ii = 0; ii < count; ii++) + { + var key = info.GetValue(Names.Key + ii.ToString(), typeof(object)); + var value = info.GetValue(Names.Value + ii.ToString(), typeof(object)); + + if (key != null) + { + dictionary_[key] = value; + } + } + } + + /// + /// Serializes a server into a stream. + /// + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(Names.KeyType, keyType_); + info.AddValue(Names.ValueValue, valueType_); + info.AddValue(Names.Count, dictionary_.Count); + + var ii = 0; + + var enumerator = dictionary_.GetEnumerator(); + + while (enumerator.MoveNext()) + { + info.AddValue(Names.Key + ii.ToString(), enumerator.Key); + info.AddValue(Names.Value + ii.ToString(), enumerator.Value); + + ii++; + } + } + #endregion + + #region IDictionary Members + /// + /// Gets a value indicating whether the IDictionary is read-only. + /// + public virtual bool IsReadOnly => false; + + /// + /// Returns an IDictionaryEnumerator for the IDictionary. + /// + public virtual IDictionaryEnumerator GetEnumerator() + { + return dictionary_.GetEnumerator(); + } + + /// + /// Gets or sets the element with the specified key. + /// + public virtual object this[object key] + { + get => dictionary_[key]; + + set + { + ValidateKey(key, keyType_); + ValidateValue(value, valueType_); + dictionary_[key] = value; + } + } + + /// + /// Removes the element with the specified key from the IDictionary. + /// + public virtual void Remove(object key) + { + dictionary_.Remove(key); + } + + /// + /// Determines whether the IDictionary contains an element with the specified key. + /// + public virtual bool Contains(object key) + { + return dictionary_.Contains(key); + } + + /// + /// Removes all elements from the IDictionary. + /// + public virtual void Clear() + { + dictionary_.Clear(); + } + + /// + /// Gets an ICollection containing the values in the IDictionary. + /// + public virtual ICollection Values => dictionary_.Values; + + /// + /// Adds an element with the provided key and value to the IDictionary. + /// + public virtual void Add(object key, object value) + { + ValidateKey(key, keyType_); + ValidateValue(value, valueType_); + dictionary_.Add(key, value); + } + + /// + /// Gets an ICollection containing the keys of the IDictionary. + /// + public virtual ICollection Keys => dictionary_.Keys; + + /// + /// Gets a value indicating whether the IDictionary has a fixed size. + /// + public virtual bool IsFixedSize => false; + + #endregion + + #region ICollection Members + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual bool IsSynchronized => false; + + /// + /// Gets the number of objects in the collection. + /// + public virtual int Count => dictionary_.Count; + + /// + /// Copies the objects to an Array, starting at a the specified index. + /// + /// The one-dimensional Array that is the destination for the objects. + /// The zero-based index in the Array at which copying begins. + public virtual void CopyTo(Array array, int index) + { + dictionary_?.CopyTo(array, index); + } + + /// + /// Indicates whether access to the ICollection is synchronized (thread-safe). + /// + public virtual object SyncRoot => this; + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator that can iterate through a collection. + /// + /// An IEnumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + #region ICloneable Members + /// + /// Creates a deep copy of the collection. + /// + public virtual object Clone() + { + var clone = (OpcWriteableDictionary)MemberwiseClone(); + + // clone contents of hashtable. + var dictionary = new Hashtable(); + + var enumerator = dictionary_.GetEnumerator(); + + while (enumerator.MoveNext()) + { + dictionary.Add(OpcConvert.Clone(enumerator.Key), OpcConvert.Clone(enumerator.Value)); + } + + clone.dictionary_ = dictionary; + + // return clone. + return clone; + } + #endregion + + } +} diff --git a/Technosoftware/DaAeHdaClient/Properties/Resources.Designer.cs b/Technosoftware/DaAeHdaClient/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ab47306 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Technosoftware.DaAeHdaClient.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Technosoftware.DaAeHdaClient.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Technosoftware/DaAeHdaClient/Properties/Resources.resx b/Technosoftware/DaAeHdaClient/Properties/Resources.resx new file mode 100644 index 0000000..7080a7d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Technosoftware/DaAeHdaClient/Resources/Strings.resx b/Technosoftware/DaAeHdaClient/Resources/Strings.resx new file mode 100644 index 0000000..2626dba --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Resources/Strings.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.0.0.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The server is running normally. + + + A fatal error has occurred within the server. + + + The server is running but has no configuration information loaded. + + + The server has been temporarily is not getting or sending data. + + + The server is running in test mode. + + + The server is running properly but is having difficulty accessing data from its data sources. + + + Unspecified error. + + + Ran out of memory. + + + The operation could not complete due to an abnormal server state. + + + The operation took too long to complete (determined by server). + + + The server is currenly processing another polled refresh for one or more of the subscriptions. + + + No subscription handles were specified in the request. + + + The hold time is too long (determined by server). + + + The continuation point is not valid. + + + The filter string is not valid. + + + The item name is no longer available in the server address space. + + + The item name does not conform the server’s syntax. + + + The item path is no longer available in the server address space. + + + The item path does not conform the server’s syntax. + + + The item name is no longer available in the server address space. + + + The item name does not conform the server’s syntax. + + + The item path is no longer available in the server address space. + + + The item path does not conform the server’s syntax. + + + The server cannot convert the data between the requested data type and the canonical data type. + + + The value was out of range. + + + The value is read only and may not be written to. + + + The value is write only and may not be read from or returned as part of a write response. + + + The server does not support writing to the quality and/or timestamp. + + + The property id is not valid for the item. + + + The property id is not valid for the item. + + + The value written was accepted but the output was clamped. + + + The server does not support the requested rate but will use the closest available rate. + + + Not every detected change has been returned since the server's buffer reached its limit and had to purge out the oldest data. + + + The remote server is not currently connected + + \ No newline at end of file diff --git a/Technosoftware/DaAeHdaClient/Technosoftware.DaAeHdaClient.csproj b/Technosoftware/DaAeHdaClient/Technosoftware.DaAeHdaClient.csproj new file mode 100644 index 0000000..dda1555 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Technosoftware.DaAeHdaClient.csproj @@ -0,0 +1,15 @@ + + + + Technosoftware.DaAeHdaClient + net6.0 + 9.0 + Technosoftware.DaAeHdaSolution.DaAeHdaClient + OPC DA/AE/HDA Client Solution .NET + + + + + + + diff --git a/Technosoftware/DaAeHdaClient/Utilities/ConfigUtils.cs b/Technosoftware/DaAeHdaClient/Utilities/ConfigUtils.cs new file mode 100644 index 0000000..7a53c42 --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Utilities/ConfigUtils.cs @@ -0,0 +1,87 @@ +#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.Text; +using System.Globalization; +using System.Diagnostics; +using System.IO; +// ReSharper disable UnusedMember.Global + +#endregion + +namespace Technosoftware.DaAeHdaClient.Utilities +{ + /// + /// Utility functions used by COM applications. + /// + internal static class ConfigUtils + { + /// + /// Gets the log file directory and ensures it is writable. + /// + public static string GetLogFileDirectory() + { + // try the program data directory. + var logFileDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + logFileDirectory += "\\Technosoftware\\Logs"; + + try + { + // create the directory. + if (!Directory.Exists(logFileDirectory)) + { + Directory.CreateDirectory(logFileDirectory); + } + } + catch (Exception) + { + // try the MyDocuments directory instead. + logFileDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + logFileDirectory += "Technosoftware\\Logs"; + + if (!Directory.Exists(logFileDirectory)) + { + Directory.CreateDirectory(logFileDirectory); + } + } + + return logFileDirectory; + } + + /// + /// Enable the trace. + /// + /// The path to use. + /// The filename. + public static void EnableTrace(string path, string filename) + { + Utils.SetTraceOutput(Utils.TraceOutput.FileOnly); + Utils.SetTraceMask(int.MaxValue); + + var logFilePath = path + "\\" + filename; + Utils.SetTraceLog(logFilePath, false); + Utils.Trace("Log File Set to: {0}", logFilePath); + } + } +} diff --git a/Technosoftware/DaAeHdaClient/Utilities/HiResClock.cs b/Technosoftware/DaAeHdaClient/Utilities/HiResClock.cs new file mode 100644 index 0000000..8aa26ea --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Utilities/HiResClock.cs @@ -0,0 +1,113 @@ +#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.Runtime.InteropServices; +#endregion + +namespace Technosoftware.DaAeHdaClient.Utilities +{ + /// + /// Produces high resolution timestamps. + /// + internal class HiResClock + { + /// + /// Returns the current UTC time (bugs in HALs on some computers can result in time jumping backwards). + /// + public static DateTime UtcNow + { + get + { + if (s_Default.m_disabled) + { + return DateTime.UtcNow; + } + + long counter; + if (NativeMethods.QueryPerformanceCounter(out counter) == 0) + { + return DateTime.UtcNow; + } + + var ticks = (counter - s_Default.m_baseline)*s_Default.m_ratio; + + return new DateTime((long)ticks + s_Default.m_offset); + } + } + + /// + /// Disables the hi-res clock (may be necessary on some machines with bugs in the HAL). + /// + public static bool Disabled + { + get => s_Default.m_disabled; + + set => s_Default.m_disabled = value; + } + + /// + /// Constructs a class. + /// + private HiResClock() + { + if (NativeMethods.QueryPerformanceFrequency(out m_frequency) == 0) + { + m_frequency = TimeSpan.TicksPerSecond; + } + + m_offset = DateTime.UtcNow.Ticks; + + if (NativeMethods.QueryPerformanceCounter(out m_baseline) == 0) + { + m_baseline = m_offset; + } + + m_ratio = ((decimal)TimeSpan.TicksPerSecond)/m_frequency; + } + + /// + /// Defines a global instance. + /// + private static readonly HiResClock s_Default = new HiResClock(); + + /// + /// Defines the native methods used by the class. + /// + private static class NativeMethods + { + [DllImport("Kernel32.dll")] + public static extern int QueryPerformanceFrequency(out long lpFrequency); + + [DllImport("Kernel32.dll")] + public static extern int QueryPerformanceCounter(out long lpFrequency); + } + + private long m_frequency; + private long m_baseline; + private long m_offset; + private decimal m_ratio; + private bool m_disabled; + } + +} diff --git a/Technosoftware/DaAeHdaClient/Utilities/Utils.cs b/Technosoftware/DaAeHdaClient/Utilities/Utils.cs new file mode 100644 index 0000000..5b63c7d --- /dev/null +++ b/Technosoftware/DaAeHdaClient/Utilities/Utils.cs @@ -0,0 +1,731 @@ +#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.Text; +using System.Globalization; +using System.Diagnostics; +using System.IO; +// ReSharper disable UnusedMember.Global + +#endregion + +namespace Technosoftware.DaAeHdaClient.Utilities +{ + /// + /// Defines various static utility functions. + /// + public static class Utils + { + #region Trace Support +#if DEBUG + private static int traceOutput_ = (int)TraceOutput.DebugAndFile; + private static int traceMasks_ = TraceMasks.All; +#else + private static int traceOutput_ = (int)TraceOutput.FileOnly; + private static int traceMasks_ = (int)TraceMasks.None; +#endif + + private static string traceFileName_; + private static long baseLineTicks_ = DateTime.UtcNow.Ticks; + private static object traceFileLock_ = new object(); + + /// + /// The possible trace output mechanisms. + /// + public enum TraceOutput + { + /// + /// No tracing + /// + Off = 0, + + /// + /// Only write to file (if specified). Default for Release mode. + /// + FileOnly = 1, + + /// + /// Write to debug trace listeners and a file (if specified). Default for Debug mode. + /// + DebugAndFile = 2, + + /// + /// Write to trace listeners and a file (if specified). + /// + StdOutAndFile = 3 + } + + /// + /// The masks used to filter trace messages. + /// + public static class TraceMasks + { + /// + /// Do not output any messages. + /// + public const int None = 0x0; + + /// + /// Output error messages. + /// + public const int Error = 0x1; + + /// + /// Output informational messages. + /// + public const int Information = 0x2; + + /// + /// Output stack traces. + /// + public const int StackTrace = 0x4; + + /// + /// Output basic messages for service calls. + /// + public const int Service = 0x8; + + /// + /// Output detailed messages for service calls. + /// + public const int ServiceDetail = 0x10; + + /// + /// Output basic messages for each operation. + /// + public const int Operation = 0x20; + + /// + /// Output detailed messages for each operation. + /// + public const int OperationDetail = 0x40; + + /// + /// Output messages related to application initialization or shutdown + /// + public const int StartStop = 0x80; + + /// + /// Output messages related to a call to an external system. + /// + public const int ExternalSystem = 0x100; + + /// + /// Output messages related to security + /// + public const int Security = 0x200; + + /// + /// Output all messages. + /// + public const int All = 0x7FFFFFFF; + } + + /// + /// Sets the output for tracing (thead safe). + /// + public static void SetTraceOutput(TraceOutput output) + { + lock (traceFileLock_) + { + traceOutput_ = (int)output; + } + } + + /// + /// Gets the current trace mask settings. + /// + public static int TraceMask => traceMasks_; + + /// + /// Sets the mask for tracing (thead safe). + /// + public static void SetTraceMask(int masks) + { + traceMasks_ = masks; + } + + /// + /// Returns Tracing class instance for event attaching. + /// + public static Tracing Tracing => Tracing.Instance; + + /// + /// Writes a trace statement. + /// + private static void TraceWriteLine(string message, params object[] args) + { + // null strings not supported. + if (string.IsNullOrEmpty(message)) + { + return; + } + + // format the message if format arguments provided. + var output = message; + + if (args != null && args.Length > 0) + { + try + { + output = string.Format(CultureInfo.InvariantCulture, message, args); + } + catch (Exception) + { + output = message; + } + } + + // write to the log file. + lock (traceFileLock_) + { + // write to debug trace listeners. + if (traceOutput_ == (int)TraceOutput.DebugAndFile) + { + Debug.WriteLine(output); + } + + // write to trace listeners. + if (traceOutput_ == (int)TraceOutput.StdOutAndFile) + { + System.Diagnostics.Trace.WriteLine(output); + } + + var traceFileName = traceFileName_; + + if (traceOutput_ != (int)TraceOutput.Off && !string.IsNullOrEmpty(traceFileName)) + { + try + { + var file = new FileInfo(traceFileName); + + // limit the file size. hard coded for now - fix later. + var truncated = false; + + if (file.Exists && file.Length > 10000000) + { + file.Delete(); + truncated = true; + } + + using (var writer = new StreamWriter(File.Open(traceFileName, FileMode.Append))) + { + if (truncated) + { + writer.WriteLine("WARNING - LOG FILE TRUNCATED."); + } + + writer.WriteLine(output); + writer.Close(); + } + } + catch (Exception e) + { + Console.WriteLine(@"Could not write to trace file. Error={0} FilePath={1}", e.Message, traceFileName); + } + } + } + } + + /// + /// Sets the path to the log file to use for tracing. + /// + public static void SetTraceLog(string filePath, bool deleteExisting) + { + // turn tracing on. + lock (traceFileLock_) + { + // check if tracing is being turned off. + if (string.IsNullOrEmpty(filePath)) + { + traceFileName_ = null; + return; + } + + traceFileName_ = GetAbsoluteFilePath(filePath, true, false, true); + + if (traceOutput_ == (int)TraceOutput.Off) + { + traceOutput_ = (int)TraceOutput.FileOnly; + } + + try + { + var file = new FileInfo(traceFileName_); + + if (deleteExisting && file.Exists) + { + file.Delete(); + } + + // write initial log message. + TraceWriteLine( + "\r\nPID:{2} {1} Logging started at {0} {1}", + DateTime.Now, + new string('*', 25), + Process.GetCurrentProcess().Id); + } + catch (Exception e) + { + TraceWriteLine(e.Message, null); + } + } + } + + /// + /// Writes an informational message to the trace log. + /// + public static void Trace(string format, params object[] args) + { + Trace(TraceMasks.Information, format, false, args); + } + + /// + /// Writes an exception/error message to the trace log. + /// + public static void Trace(Exception e, string format, params object[] args) + { + Trace(e, format, false, args); + } + + /// + /// Writes a general message to the trace log. + /// + public static void Trace(int traceMask, string format, params object[] args) + { + Trace(traceMask, format, false, args); + } + + /// + /// Writes an exception/error message to the trace log. + /// + internal static void Trace(Exception e, string format, bool handled, params object[] args) + { + var message = new StringBuilder(); + + // format message. + if (args != null && args.Length > 0) + { + try + { + message.AppendFormat(CultureInfo.InvariantCulture, format, args); + } + catch (Exception) + { + message.Append(format); + } + } + else + { + message.Append(format); + } + + // append exception information. + if (e != null) + { + if (e is OpcResultException sre) + { + message.AppendFormat(CultureInfo.InvariantCulture, " {0} '{1}'", sre.Result.Code, sre.Message); + } + else + { + message.AppendFormat(CultureInfo.InvariantCulture, " {0} '{1}'", e.GetType().Name, e.Message); + } + + // append stack trace. + if ((traceMasks_ & TraceMasks.StackTrace) != 0) + { + message.AppendFormat(CultureInfo.InvariantCulture, "\r\n\r\n{0}\r\n", new string('=', 40)); + message.Append(e.StackTrace); + message.AppendFormat(CultureInfo.InvariantCulture, "\r\n{0}\r\n", new string('=', 40)); + } + } + + // trace message. + Trace(TraceMasks.Error, message.ToString(), handled, null); + } + + /// + /// Writes the message to the trace log. + /// + private static void Trace(int traceMask, string format, bool handled, params object[] args) + { + if (!handled) + { + Tracing.Instance.RaiseTraceEvent(new TraceEventArgs(traceMask, format, string.Empty, null, args)); + } + + // do nothing if mask not enabled. + if ((traceMasks_ & traceMask) == 0) + { + return; + } + + var message = new StringBuilder(); + + // append process and timestamp. + message.AppendFormat("{0} - ", Process.GetCurrentProcess().Id); + message.AppendFormat("{0:d} {0:HH:mm:ss.fff} ", HiResClock.UtcNow.ToLocalTime()); + + // format message. + if (args != null && args.Length > 0) + { + try + { + message.AppendFormat(CultureInfo.InvariantCulture, format, args); + } + catch (Exception) + { + message.Append(format); + } + } + else + { + message.Append(format); + } + + TraceWriteLine(message.ToString(), null); + } + + #endregion + + #region File Access + + /// + /// Replaces a prefix enclosed in '%' with a special folder or environment variable path (e.g. %ProgramFiles%\MyCompany). + /// + public static string ReplaceSpecialFolderNames(string input) + { + // nothing to do for nulls. + if (string.IsNullOrEmpty(input)) + { + return null; + } + + // check for absolute path. + if (input.Length > 1 && ((input[0] == '\\' && input[1] == '\\') || input[1] == ':')) + { + return input; + } + + // check for special folder prefix. + if (input[0] != '%') + { + return input; + } + + // extract special folder name. + string folder; + string path; + + var index = input.IndexOf('%', 1); + + if (index == -1) + { + folder = input.Substring(1); + path = string.Empty; + } + else + { + folder = input.Substring(1, index - 1); + path = input.Substring(index + 1); + } + + var buffer = new StringBuilder(); + + // check for special folder. + try + { + var specialFolder = (Environment.SpecialFolder)Enum.Parse( + typeof(Environment.SpecialFolder), + folder, + true); + + buffer.Append(Environment.GetFolderPath(specialFolder)); + } + + // check for generic environment variable. + catch (Exception) + { + var value = Environment.GetEnvironmentVariable(folder); + + if (value != null) + { + buffer.Append(value); + } + } + + // construct new path. + buffer.Append(path); + return buffer.ToString(); + } + + /// + /// Checks if the file path is a relative path and returns an absolute path relative to the EXE location. + /// + public static string GetAbsoluteFilePath(string filePath) + { + return GetAbsoluteFilePath(filePath, false, true, false); + } + + /// + /// Checks if the file path is a relative path and returns an absolute path relative to the EXE location. + /// + public static string GetAbsoluteFilePath(string filePath, bool checkCurrentDirectory, bool throwOnError, bool createAlways) + { + filePath = ReplaceSpecialFolderNames(filePath); + + if (!string.IsNullOrEmpty(filePath)) + { + var file = new FileInfo(filePath); + + // check for absolute path. + var isAbsolute = filePath.StartsWith("\\\\", StringComparison.Ordinal) || filePath.IndexOf(':') == 1; + + if (isAbsolute) + { + if (file.Exists) + { + return filePath; + } + + if (createAlways) + { + return CreateFile(file, filePath, throwOnError); + } + } + + if (!isAbsolute) + { + // look current directory. + if (checkCurrentDirectory) + { + if (!file.Exists) + { + file = new FileInfo(Format("{0}\\{1}", Environment.CurrentDirectory, filePath)); + } + + if (file.Exists) + { + return file.FullName; + } + + if (createAlways) + { + return CreateFile(file, filePath, throwOnError); + } + } + + // look executable directory. + if (!file.Exists) + { + var executablePath = Environment.GetCommandLineArgs()[0]; + var executable = new FileInfo(executablePath); + + if (executable.Exists) + { + file = new FileInfo(Format("{0}\\{1}", executable.DirectoryName, filePath)); + } + + if (file.Exists) + { + return file.FullName; + } + + if (createAlways) + { + return CreateFile(file, filePath, throwOnError); + } + } + } + } + + // file does not exist. + if (throwOnError) + { + throw new OpcResultException(new OpcResult(OpcResult.CONNECT_E_NOCONNECTION.Code, OpcResult.FuncCallType.SysFuncCall, null), + $"File does not exist: {filePath}\r\nCurrent directory is: {Environment.CurrentDirectory}"); + } + + return null; + } + + /// + /// Creates an empty file. + /// + private static string CreateFile(FileInfo file, string filePath, bool throwOnError) + { + try + { + // create the directory as required. + if (file.Directory != null && !file.Directory.Exists) + { + Directory.CreateDirectory(file.DirectoryName); + } + + // open and close the file. + using (file.Open(FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite)) + { + return filePath; + } + } + catch (Exception) + { + if (throwOnError) + { + throw; + } + + return filePath; + } + } + + /// + /// Formats a message using the invariant locale. + /// + public static string Format(string text, params object[] args) + { + return string.Format(CultureInfo.InvariantCulture, text, args); + } + #endregion + } + + #region Tracing Class + /// + /// Used as underlying tracing object for event processing. + /// + public class Tracing + { + #region Private Members + private static object syncRoot_ = new object(); + private static Tracing instance_; + #endregion Private Members + + #region Singleton Instance + /// + /// Private constructor. + /// + private Tracing() + { } + + /// + /// Internal Singleton Instance getter. + /// + internal static Tracing Instance + { + get + { + if (instance_ == null) + { + lock (syncRoot_) + { + if (instance_ == null) + { + instance_ = new Tracing(); + } + } + } + return instance_; + } + } + #endregion Singleton Instance + + #region Public Events + /// + /// Occurs when a trace call is made. + /// + public event EventHandler TraceEventHandler; + #endregion Public Events + + internal void RaiseTraceEvent(TraceEventArgs eventArgs) + { + if (TraceEventHandler != null) + { + try + { + TraceEventHandler(this, eventArgs); + } + catch (Exception ex) + { + Utils.Trace(ex, "Exception invoking Trace Event Handler", true, null); + } + } + } + } + #endregion Tracing Class + + #region TraceEventArgs Class + /// + /// The event arguments provided when a trace event is raised. + /// + public class TraceEventArgs : EventArgs + { + #region Constructors + /// + /// Initializes a new instance of the TraceEventArgs class. + /// + /// The trace mask. + /// The format. + /// The message. + /// The exception. + /// The arguments. + internal TraceEventArgs(int traceMask, string format, string message, Exception exception, object[] args) + { + TraceMask = traceMask; + Format = format; + Message = message; + Exception = exception; + Arguments = args; + } + #endregion Constructors + + #region Public Properties + /// + /// Gets the trace mask. + /// + public int TraceMask { get; } + + /// + /// Gets the format. + /// + public string Format { get; } + + /// + /// Gets the arguments. + /// + public object[] Arguments { get; } + + /// + /// Gets the message. + /// + public string Message { get; } + + /// + /// Gets the exception. + /// + public Exception Exception { get; } + #endregion Public Properties + } + #endregion TraceEventArgs Class +} diff --git a/Technosoftware/OpcRcw/Ae/AlarmsAndEvents.cs b/Technosoftware/OpcRcw/Ae/AlarmsAndEvents.cs new file mode 100644 index 0000000..fa6a6be --- /dev/null +++ b/Technosoftware/OpcRcw/Ae/AlarmsAndEvents.cs @@ -0,0 +1,800 @@ +#region Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +//----------------------------------------------------------------------------- +// Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +// Web: https://technosoftware.com +// +// The Software is based on the OPC Foundation MIT License. +// The complete license agreement for that can be found here: +// http://opcfoundation.org/License/MIT/1.00/ +//----------------------------------------------------------------------------- +#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved + +#region Using Directives +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +#endregion + +#pragma warning disable 1591 + +namespace Technosoftware.OpcRcw.Ae +{ + /// + [ComImport] + [GuidAttribute("58E13251-AC87-11d1-84D5-00608CB8A7E9")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface CATID_OPCAEServer10 {} + + /// + public enum OPCAEBROWSEDIRECTION + { + OPCAE_BROWSE_UP = 1, + OPCAE_BROWSE_DOWN, + OPCAE_BROWSE_TO + } + + /// + public enum OPCAEBROWSETYPE + { + OPC_AREA = 1, + OPC_SOURCE + } + + /// + public enum OPCEVENTSERVERSTATE + { + OPCAE_STATUS_RUNNING = 1, + OPCAE_STATUS_FAILED, + OPCAE_STATUS_NOCONFIG, + OPCAE_STATUS_SUSPENDED, + OPCAE_STATUS_TEST, + OPCAE_STATUS_COMM_FAULT + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct FILETIME + { + public int dwLowDateTime; + public int dwHighDateTime; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct ONEVENTSTRUCT + { + [MarshalAs(UnmanagedType.I2)] + public short wChangeMask; + [MarshalAs(UnmanagedType.I2)] + public short wNewState; + [MarshalAs(UnmanagedType.LPWStr)] + public string szSource; + public FILETIME ftTime; + [MarshalAs(UnmanagedType.LPWStr)] + public string szMessage; + [MarshalAs(UnmanagedType.I4)] + public int dwEventType; + [MarshalAs(UnmanagedType.I4)] + public int dwEventCategory; + [MarshalAs(UnmanagedType.I4)] + public int dwSeverity; + [MarshalAs(UnmanagedType.LPWStr)] + public string szConditionName; + [MarshalAs(UnmanagedType.LPWStr)] + public string szSubconditionName; + [MarshalAs(UnmanagedType.I2)] + public short wQuality; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + [MarshalAs(UnmanagedType.I4)] + public int bAckRequired; + public FILETIME ftActiveTime; + [MarshalAs(UnmanagedType.I4)] + public int dwCookie; + [MarshalAs(UnmanagedType.I4)] + public int dwNumEventAttrs; + public IntPtr pEventAttributes; + [MarshalAs(UnmanagedType.LPWStr)] + public string szActorID; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCEVENTSERVERSTATUS + { + public FILETIME ftStartTime; + public FILETIME ftCurrentTime; + public FILETIME ftLastUpdateTime; + public OPCEVENTSERVERSTATE dwServerState; + [MarshalAs(UnmanagedType.I2)] + public short wMajorVersion; + [MarshalAs(UnmanagedType.I2)] + public short wMinorVersion; + [MarshalAs(UnmanagedType.I2)] + public short wBuildNumber; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + [MarshalAs(UnmanagedType.LPWStr)] + public string szVendorInfo; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCCONDITIONSTATE + { + [MarshalAs(UnmanagedType.I2)] + public short wState; + [MarshalAs(UnmanagedType.I2)] + public short wReserved1; + [MarshalAs(UnmanagedType.LPWStr)] + public string szActiveSubCondition; + [MarshalAs(UnmanagedType.LPWStr)] + public string szASCDefinition; + [MarshalAs(UnmanagedType.I4)] + public int dwASCSeverity; + [MarshalAs(UnmanagedType.LPWStr)] + public string szASCDescription; + [MarshalAs(UnmanagedType.I2)] + public short wQuality; + [MarshalAs(UnmanagedType.I2)] + public short wReserved2; + public FILETIME ftLastAckTime; + public FILETIME ftSubCondLastActive; + public FILETIME ftCondLastActive; + public FILETIME ftCondLastInactive; + [MarshalAs(UnmanagedType.LPWStr)] + public string szAcknowledgerID; + [MarshalAs(UnmanagedType.LPWStr)] + public string szComment; + [MarshalAs(UnmanagedType.I4)] + public int dwNumSCs; + public IntPtr pszSCNames; + public IntPtr pszSCDefinitions; + public IntPtr pdwSCSeverities; + public IntPtr pszSCDescriptions; + public int dwNumEventAttrs; + public IntPtr pEventAttributes; + public IntPtr pErrors; + } + + /// + [ComImport] + [GuidAttribute("65168851-5783-11D1-84A0-00608CB8A7E9")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCEventServer + { + void GetStatus( + out IntPtr ppEventServerStatus); + + void CreateEventSubscription( + [MarshalAs(UnmanagedType.I4)] + int bActive, + [MarshalAs(UnmanagedType.I4)] + int dwBufferTime, + [MarshalAs(UnmanagedType.I4)] + int dwMaxSize, + [MarshalAs(UnmanagedType.I4)] + int hClientSubscription, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=4)] + out object ppUnk, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedBufferTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedMaxSize); + + void QueryAvailableFilters( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwFilterMask); + + void QueryEventCategories( + [MarshalAs(UnmanagedType.I4)] + int dwEventType, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwEventCategories, + [Out] + out IntPtr ppszEventCategoryDescs); + + [PreserveSig] + int QueryConditionNames( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppszConditionNames); + + void QuerySubConditionNames( + [MarshalAs(UnmanagedType.LPWStr)] + string szConditionName, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppszSubConditionNames); + + void QuerySourceConditions( + [MarshalAs(UnmanagedType.LPWStr)] + string szSource, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppszConditionNames); + + void QueryEventAttributes( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwAttrIDs, + [Out] + out IntPtr ppszAttrDescs, + [Out] + out IntPtr ppvtAttrTypes); + + void TranslateToItemIDs( + [MarshalAs(UnmanagedType.LPWStr)] + string szSource, + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [MarshalAs(UnmanagedType.LPWStr)] + string szConditionName, + [MarshalAs(UnmanagedType.LPWStr)] + string szSubconditionName, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] pdwAssocAttrIDs, + out IntPtr ppszAttrItemIDs, + out IntPtr ppszNodeNames, + out IntPtr ppCLSIDs); + + void GetConditionState( + [MarshalAs(UnmanagedType.LPWStr)] + string szSource, + [MarshalAs(UnmanagedType.LPWStr)] + string szConditionName, + [MarshalAs(UnmanagedType.I4)] + int dwNumEventAttrs, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] pdwAttributeIDs, + [Out] + out IntPtr ppConditionState); + + void EnableConditionByArea( + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszAreas); + + void EnableConditionBySource( + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSources); + + void DisableConditionByArea( + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszAreas); + + void DisableConditionBySource( + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSources); + + void AckCondition( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPWStr)] + string szAcknowledgerID, + [MarshalAs(UnmanagedType.LPWStr)] + string szComment, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSource, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] szConditionName, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + FILETIME[] pftActiveTime, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwCookie, + [Out] + out IntPtr ppErrors); + + void CreateAreaBrowser( + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=0)] + out object ppUnk); + } + + /// + [ComImport] + [GuidAttribute("65168855-5783-11D1-84A0-00608CB8A7E9")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCEventSubscriptionMgt + { + void SetFilter( + [MarshalAs(UnmanagedType.I4)] + int dwEventType, + [MarshalAs(UnmanagedType.I4)] + int dwNumCategories, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] pdwEventCategories, + [MarshalAs(UnmanagedType.I4)] + int dwLowSeverity, + [MarshalAs(UnmanagedType.I4)] + int dwHighSeverity, + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=5)] + string[] pszAreaList, + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=7)] + string[] pszSourceList); + + void GetFilter( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwEventType, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwNumCategories, + [Out] + out IntPtr ppdwEventCategories, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwLowSeverity, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwHighSeverity, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwNumAreas, + [Out] + out IntPtr ppszAreaList, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwNumSources, + [Out] + out IntPtr ppszSourceList); + + void SelectReturnedAttributes( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] dwAttributeIDs); + + void GetReturnedAttributes( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwAttributeIDs); + + void Refresh( + [MarshalAs(UnmanagedType.I4)] + int dwConnection); + + void CancelRefresh( + [MarshalAs(UnmanagedType.I4)] + int dwConnection); + + void GetState( + [Out][MarshalAs(UnmanagedType.I4)] + out int pbActive, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwBufferTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwMaxSize, + [Out][MarshalAs(UnmanagedType.I4)] + out int phClientSubscription); + + void SetState( + IntPtr pbActive, + IntPtr pdwBufferTime, + IntPtr pdwMaxSize, + [MarshalAs(UnmanagedType.I4)] + int hClientSubscription, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedBufferTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedMaxSize); + } + + /// + [ComImport] + [GuidAttribute("65168857-5783-11D1-84A0-00608CB8A7E9")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCEventAreaBrowser + { + void ChangeBrowsePosition( + OPCAEBROWSEDIRECTION dwBrowseDirection, + [MarshalAs(UnmanagedType.LPWStr)] + string szString); + + void BrowseOPCAreas( + OPCAEBROWSETYPE dwBrowseFilterType, + [MarshalAs(UnmanagedType.LPWStr)] + string szFilterCriteria, + [Out] + out OpcRcw.Comn.IEnumString ppIEnumString); + + void GetQualifiedAreaName( + [MarshalAs(UnmanagedType.LPWStr)] + string szAreaName, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string pszQualifiedAreaName); + + void GetQualifiedSourceName( + [MarshalAs(UnmanagedType.LPWStr)] + string szSourceName, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string pszQualifiedSourceName); + } + + /// + [ComImport] + [GuidAttribute("6516885F-5783-11D1-84A0-00608CB8A7E9")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCEventSink + { + void OnEvent( + [MarshalAs(UnmanagedType.I4)] + int hClientSubscription, + [MarshalAs(UnmanagedType.I4)] + int bRefresh, + [MarshalAs(UnmanagedType.I4)] + int bLastRefresh, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=3)] + ONEVENTSTRUCT[] pEvents); + } + + /// + [ComImport] + [GuidAttribute("71BBE88E-9564-4bcd-BCFC-71C558D94F2D")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCEventServer2 // : IOPCEventServer + { + void GetStatus( + out IntPtr ppEventServerStatus); + + void CreateEventSubscription( + [MarshalAs(UnmanagedType.I4)] + int bActive, + [MarshalAs(UnmanagedType.I4)] + int dwBufferTime, + [MarshalAs(UnmanagedType.I4)] + int dwMaxSize, + [MarshalAs(UnmanagedType.I4)] + int hClientSubscription, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=4)] + out object ppUnk, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedBufferTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedMaxSize); + + void QueryAvailableFilters( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwFilterMask); + + void QueryEventCategories( + [MarshalAs(UnmanagedType.I4)] + int dwEventType, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwEventCategories, + [Out] + out IntPtr ppszEventCategoryDescs); + + [PreserveSig] + int QueryConditionNames( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppszConditionNames); + + void QuerySubConditionNames( + [MarshalAs(UnmanagedType.LPWStr)] + string szConditionName, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppszSubConditionNames); + + void QuerySourceConditions( + [MarshalAs(UnmanagedType.LPWStr)] + string szSource, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppszConditionNames); + + void QueryEventAttributes( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwAttrIDs, + [Out] + out IntPtr ppszAttrDescs, + [Out] + out IntPtr ppvtAttrTypes); + + void TranslateToItemIDs( + [MarshalAs(UnmanagedType.LPWStr)] + string szSource, + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [MarshalAs(UnmanagedType.LPWStr)] + string szConditionName, + [MarshalAs(UnmanagedType.LPWStr)] + string szSubconditionName, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] pdwAssocAttrIDs, + out IntPtr ppszAttrItemIDs, + out IntPtr ppszNodeNames, + out IntPtr ppCLSIDs); + + void GetConditionState( + [MarshalAs(UnmanagedType.LPWStr)] + string szSource, + [MarshalAs(UnmanagedType.LPWStr)] + string szConditionName, + [MarshalAs(UnmanagedType.I4)] + int dwNumEventAttrs, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] pdwAttributeIDs, + [Out] + out IntPtr ppConditionState); + + void EnableConditionByArea( + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszAreas); + + void EnableConditionBySource( + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSources); + + void DisableConditionByArea( + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszAreas); + + void DisableConditionBySource( + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSources); + + void AckCondition( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPWStr)] + string szAcknowledgerID, + [MarshalAs(UnmanagedType.LPWStr)] + string szComment, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSource, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] szConditionName, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + FILETIME[] pftActiveTime, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwCookie, + [Out] + out IntPtr ppErrors); + + void CreateAreaBrowser( + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=0)] + out object ppUnk); + + void EnableConditionByArea2( + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszAreas, + [Out] + out IntPtr ppErrors); + + void EnableConditionBySource2( + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSources, + [Out] + out IntPtr ppErrors); + + void DisableConditionByArea2( + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszAreas, + [Out] + out IntPtr ppErrors); + + void DisableConditionBySource2( + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSources, + [Out] + out IntPtr ppErrors); + + void GetEnableStateByArea( + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszAreas, + [Out] + out IntPtr pbEnabled, + [Out] + out IntPtr pbEffectivelyEnabled, + [Out] + out IntPtr ppErrors); + + void GetEnableStateBySource( + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszSources, + out IntPtr pbEnabled, + out IntPtr pbEffectivelyEnabled, + out IntPtr ppErrors); + }; + + /// + [ComImport] + [GuidAttribute("94C955DC-3684-4ccb-AFAB-F898CE19AAC3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCEventSubscriptionMgt2 // : IOPCEventSubscriptionMgt + { + void SetFilter( + [MarshalAs(UnmanagedType.I4)] + int dwEventType, + [MarshalAs(UnmanagedType.I4)] + int dwNumCategories, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] pdwEventCategories, + [MarshalAs(UnmanagedType.I4)] + int dwLowSeverity, + [MarshalAs(UnmanagedType.I4)] + int dwHighSeverity, + [MarshalAs(UnmanagedType.I4)] + int dwNumAreas, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=5)] + string[] pszAreaList, + [MarshalAs(UnmanagedType.I4)] + int dwNumSources, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=7)] + string[] pszSourceList); + + void GetFilter( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwEventType, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwNumCategories, + [Out] + out IntPtr ppdwEventCategories, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwLowSeverity, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwHighSeverity, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwNumAreas, + [Out] + out IntPtr ppszAreaList, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwNumSources, + [Out] + out IntPtr ppszSourceList); + + void SelectReturnedAttributes( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] dwAttributeIDs); + + void GetReturnedAttributes( + [MarshalAs(UnmanagedType.I4)] + int dwEventCategory, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwAttributeIDs); + + void Refresh( + [MarshalAs(UnmanagedType.I4)] + int dwConnection); + + void CancelRefresh( + [MarshalAs(UnmanagedType.I4)] + int dwConnection); + + void GetState( + [Out][MarshalAs(UnmanagedType.I4)] + out int pbActive, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwBufferTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwMaxSize, + [Out][MarshalAs(UnmanagedType.I4)] + out int phClientSubscription); + + void SetState( + IntPtr pbActive, + IntPtr pdwBufferTime, + IntPtr pdwMaxSize, + [MarshalAs(UnmanagedType.I4)] + int hClientSubscription, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedBufferTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedMaxSize); + + void SetKeepAlive( + [MarshalAs(UnmanagedType.I4)] + int dwKeepAliveTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedKeepAliveTime); + + void GetKeepAlive( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwKeepAliveTime); + } + + /// + public static class Constants + { + // category description string. + public const string OPC_CATEGORY_DESCRIPTION_AE10 = "OPC Alarm & Event Server Version 1.0"; + + // state bit masks. + public const int CONDITION_ENABLED = 0x0001; + public const int CONDITION_ACTIVE = 0x0002; + public const int CONDITION_ACKED = 0x0004; + + // bit masks for change mask. + public const int CHANGE_ACTIVE_STATE = 0x0001; + public const int CHANGE_ACK_STATE = 0x0002; + public const int CHANGE_ENABLE_STATE = 0x0004; + public const int CHANGE_QUALITY = 0x0008; + public const int CHANGE_SEVERITY = 0x0010; + public const int CHANGE_SUBCONDITION = 0x0020; + public const int CHANGE_MESSAGE = 0x0040; + public const int CHANGE_ATTRIBUTE = 0x0080; + + // event type. + public const int SIMPLE_EVENT = 0x0001; + public const int TRACKING_EVENT = 0x0002; + public const int CONDITION_EVENT = 0x0004; + public const int ALL_EVENTS = 0x0007; + + // bit masks for QueryAvailableFilters(). + public const int FILTER_BY_EVENT = 0x0001; + public const int FILTER_BY_CATEGORY = 0x0002; + public const int FILTER_BY_SEVERITY = 0x0004; + public const int FILTER_BY_AREA = 0x0008; + public const int FILTER_BY_SOURCE = 0x0010; + } +} diff --git a/Technosoftware/OpcRcw/Comn/Common.cs b/Technosoftware/OpcRcw/Comn/Common.cs new file mode 100644 index 0000000..fc8e12b --- /dev/null +++ b/Technosoftware/OpcRcw/Comn/Common.cs @@ -0,0 +1,361 @@ +#region Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +//----------------------------------------------------------------------------- +// Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +// Web: https://technosoftware.com +// +// The Software is based on the OPC Foundation MIT License. +// The complete license agreement for that can be found here: +// http://opcfoundation.org/License/MIT/1.00/ +//----------------------------------------------------------------------------- +#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved + +#region Using Directives +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; + +/* Unmerged change from project 'Technosoftware.OpcRcw (net472)' +Before: +#endregion +After: +using Technosoftware.OpcRcw; +using Technosoftware.OpcRcw.Comn; +using Technosoftware.OpcRcw; +#endregion +*/ +#endregion + +#pragma warning disable 1591 + +namespace Technosoftware.OpcRcw.Comn +{ + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct CONNECTDATA + { + [MarshalAs(UnmanagedType.IUnknown)] + object pUnk; + [MarshalAs(UnmanagedType.I4)] + int dwCookie; + } + + /// + [ComImport] + [Guid("B196B287-BAB4-101A-B69C-00AA00341D07")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IEnumConnections + { + /// + /// Retrieves a specified number of items in the enumeration sequence. + /// + /// + /// + /// + void RemoteNext( + [MarshalAs(UnmanagedType.I4)] + int cConnections, + [Out] + IntPtr rgcd, + [Out][MarshalAs(UnmanagedType.I4)] + out int pcFetched); + + /// + /// Skips a specified number of items in the enumeration sequence. + /// + /// + void Skip( + [MarshalAs(UnmanagedType.I4)] + int cConnections); + + /// + /// Retrieves a specified number of items in the enumeration sequence. + /// + void Reset(); + + /// + /// Creates a new enumerator that contains the same enumeration state as the current one. + /// + /// + void Clone( + [Out] + out IEnumConnections ppEnum); + } + + /// + [ComImport] + [Guid("B196B286-BAB4-101A-B69C-00AA00341D07")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IConnectionPoint + { + void GetConnectionInterface( + [Out] + out Guid pIID); + + void GetConnectionPointContainer( + [Out] + out IConnectionPointContainer ppCPC); + + void Advise( + [MarshalAs(UnmanagedType.IUnknown)] + object pUnkSink, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCookie); + + void Unadvise( + [MarshalAs(UnmanagedType.I4)] + int dwCookie); + + void EnumConnections( + [Out] + out IEnumConnections ppEnum); + } + + /// + [ComImport] + [Guid("B196B285-BAB4-101A-B69C-00AA00341D07")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IEnumConnectionPoints + { + void RemoteNext( + [MarshalAs(UnmanagedType.I4)] + int cConnections, + [Out] + IntPtr ppCP, + [Out][MarshalAs(UnmanagedType.I4)] + out int pcFetched); + + void Skip( + [MarshalAs(UnmanagedType.I4)] + int cConnections); + + void Reset(); + + void Clone( + [Out] + out IEnumConnectionPoints ppEnum); + } + + /// + [ComImport] + [Guid("B196B284-BAB4-101A-B69C-00AA00341D07")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IConnectionPointContainer + { + void EnumConnectionPoints( + [Out] + out IEnumConnectionPoints ppEnum); + + void FindConnectionPoint( + ref Guid riid, + [Out] + out IConnectionPoint ppCP); + } + + /// + [ComImport] + [Guid("F31DFDE1-07B6-11d2-B2D8-0060083BA1FB")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCShutdown + { + void ShutdownRequest( + [MarshalAs(UnmanagedType.LPWStr)] + string szReason); + } + + /// + [ComImport] + [Guid("F31DFDE2-07B6-11d2-B2D8-0060083BA1FB")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCCommon + { + void SetLocaleID( + [MarshalAs(UnmanagedType.I4)] + int dwLcid); + + void GetLocaleID( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwLcid); + + void QueryAvailableLocaleIDs( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr pdwLcid); + + void GetErrorString( + [MarshalAs(UnmanagedType.I4)] + int dwError, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppString); + + void SetClientName( + [MarshalAs(UnmanagedType.LPWStr)] + string szName); + } + + /// + [ComImport] + [Guid("13486D50-4821-11D2-A494-3CB306C10000")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCServerList + { + void EnumClassesOfCategories( + [MarshalAs(UnmanagedType.I4)] + int cImplemented, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + Guid[] rgcatidImpl, + [MarshalAs(UnmanagedType.I4)] + int cRequired, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=2)] + Guid[] rgcatidReq, + [Out][MarshalAs(UnmanagedType.IUnknown)] + out object ppenumClsid); + + void GetClassDetails( + ref Guid clsid, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppszProgID, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppszUserType); + + void CLSIDFromProgID( + [MarshalAs(UnmanagedType.LPWStr)] + string szProgId, + [Out] + out Guid clsid); + } + + /// + [ComImport] + [Guid("55C382C8-21C7-4e88-96C1-BECFB1E3F483")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCEnumGUID + { + void Next( + [MarshalAs(UnmanagedType.I4)] + int celt, + [Out] + IntPtr rgelt, + [Out][MarshalAs(UnmanagedType.I4)] + out int pceltFetched); + + void Skip( + [MarshalAs(UnmanagedType.I4)] + int celt); + + void Reset(); + + void Clone( + [Out] + out IOPCEnumGUID ppenum); + } + + /// + [ComImport] + [Guid("0002E000-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IEnumGUID + { + void Next( + [MarshalAs(UnmanagedType.I4)] + int celt, + [Out] + IntPtr rgelt, + [Out][MarshalAs(UnmanagedType.I4)] + out int pceltFetched); + + void Skip( + [MarshalAs(UnmanagedType.I4)] + int celt); + + void Reset(); + + void Clone( + [Out] + out IEnumGUID ppenum); + } + + /// + [ComImport] + [Guid("00000100-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IEnumUnknown + { + void RemoteNext( + [MarshalAs(UnmanagedType.I4)] + int celt, + [Out] + IntPtr rgelt, + [Out][MarshalAs(UnmanagedType.I4)] + out int pceltFetched); + + void Skip( + [MarshalAs(UnmanagedType.I4)] + int celt); + + void Reset(); + + void Clone( + [Out] + out IEnumUnknown ppenum); + } + + /// + [ComImport] + [Guid("00000101-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IEnumString + { + void RemoteNext( + [MarshalAs(UnmanagedType.I4)] + int celt, + IntPtr rgelt, + [Out][MarshalAs(UnmanagedType.I4)] + out int pceltFetched); + + void Skip( + [MarshalAs(UnmanagedType.I4)] + int celt); + + void Reset(); + + void Clone( + [Out] + out IEnumString ppenum); + } + + /// + [ComImport] + [Guid("9DD0B56C-AD9E-43ee-8305-487F3188BF7A")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCServerList2 + { + void EnumClassesOfCategories( + [MarshalAs(UnmanagedType.I4)] + int cImplemented, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + Guid[] rgcatidImpl, + [MarshalAs(UnmanagedType.I4)] + int cRequired, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + Guid[] rgcatidReq, + [Out] + out IOPCEnumGUID ppenumClsid); + + void GetClassDetails( + ref Guid clsid, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppszProgID, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppszUserType, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppszVerIndProgID); + + void CLSIDFromProgID( + [MarshalAs(UnmanagedType.LPWStr)] + string szProgId, + [Out] + out Guid clsid); + } +} diff --git a/Technosoftware/OpcRcw/Da/DataAccess.cs b/Technosoftware/OpcRcw/Da/DataAccess.cs new file mode 100644 index 0000000..041fd02 --- /dev/null +++ b/Technosoftware/OpcRcw/Da/DataAccess.cs @@ -0,0 +1,1417 @@ +#region Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +//----------------------------------------------------------------------------- +// Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +// Web: https://technosoftware.com +// +// The Software is based on the OPC Foundation MIT License. +// The complete license agreement for that can be found here: +// http://opcfoundation.org/License/MIT/1.00/ +//----------------------------------------------------------------------------- +#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved + +#region Using Directives +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +#endregion + +#pragma warning disable 1591, CS0618 + +namespace Technosoftware.OpcRcw.Da +{ + /// + [ComImport] + [GuidAttribute("63D5F430-CFE4-11d1-B2C8-0060083BA1FB")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface CATID_OPCDAServer10 {} + + /// + [ComImport] + [GuidAttribute("63D5F432-CFE4-11d1-B2C8-0060083BA1FB")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface CATID_OPCDAServer20 {} + + /// + [ComImport] + [GuidAttribute("CC603642-66D7-48f1-B69A-B625E73652D7")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface CATID_OPCDAServer30 {} + + /// + [ComImport] + [GuidAttribute("3098EDA4-A006-48b2-A27F-247453959408")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface CATID_XMLDAServer10 {} + + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct FILETIME + { + public int dwLowDateTime; + public int dwHighDateTime; + } + + /// + public enum OPCDATASOURCE + { + OPC_DS_CACHE = 1, + OPC_DS_DEVICE + } + + /// + public enum OPCBROWSETYPE + { + OPC_BRANCH = 1, + OPC_LEAF, + OPC_FLAT + } + + /// + public enum OPCNAMESPACETYPE + { + OPC_NS_HIERARCHIAL = 1, + OPC_NS_FLAT + } + + /// + public enum OPCBROWSEDIRECTION + { + OPC_BROWSE_UP = 1, + OPC_BROWSE_DOWN, + OPC_BROWSE_TO + } + + /// + public enum OPCEUTYPE + { + OPC_NOENUM = 0, + OPC_ANALOG, + OPC_ENUMERATED + } + + /// + public enum OPCSERVERSTATE + { + OPC_STATUS_RUNNING = 1, + OPC_STATUS_FAILED, + OPC_STATUS_NOCONFIG, + OPC_STATUS_SUSPENDED, + OPC_STATUS_TEST, + OPC_STATUS_COMM_FAULT + } + + /// + public enum OPCENUMSCOPE + { + OPC_ENUM_PRIVATE_CONNECTIONS = 1, + OPC_ENUM_PUBLIC_CONNECTIONS, + OPC_ENUM_ALL_CONNECTIONS, + OPC_ENUM_PRIVATE, + OPC_ENUM_PUBLIC, + OPC_ENUM_ALL + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCGROUPHEADER + { + [MarshalAs(UnmanagedType.I4)] + public int dwSize; + [MarshalAs(UnmanagedType.I4)] + public int dwItemCount; + [MarshalAs(UnmanagedType.I4)] + public int hClientGroup; + [MarshalAs(UnmanagedType.I4)] + public int dwTransactionID; + [MarshalAs(UnmanagedType.I4)] + public int hrStatus; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMHEADER1 + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int dwValueOffset; + [MarshalAs(UnmanagedType.I2)] + public short wQuality; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + public FILETIME ftTimeStampItem; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMHEADER2 + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int dwValueOffset; + [MarshalAs(UnmanagedType.I2)] + public short wQuality; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCGROUPHEADERWRITE + { + [MarshalAs(UnmanagedType.I4)] + public int dwItemCount; + [MarshalAs(UnmanagedType.I4)] + public int hClientGroup; + [MarshalAs(UnmanagedType.I4)] + public int dwTransactionID; + [MarshalAs(UnmanagedType.I4)] + public int hrStatus; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMHEADERWRITE + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int dwError; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMSTATE + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + public FILETIME ftTimeStamp; + [MarshalAs(UnmanagedType.I2)] + public short wQuality; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + [MarshalAs(UnmanagedType.Struct)] + public object vDataValue; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCSERVERSTATUS + { + public FILETIME ftStartTime; + public FILETIME ftCurrentTime; + public FILETIME ftLastUpdateTime; + public OPCSERVERSTATE dwServerState; + [MarshalAs(UnmanagedType.I4)] + public int dwGroupCount; + [MarshalAs(UnmanagedType.I4)] + public int dwBandWidth; + [MarshalAs(UnmanagedType.I2)] + public short wMajorVersion; + [MarshalAs(UnmanagedType.I2)] + public short wMinorVersion; + [MarshalAs(UnmanagedType.I2)] + public short wBuildNumber; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + [MarshalAs(UnmanagedType.LPWStr)] + public string szVendorInfo; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMDEF + { + [MarshalAs(UnmanagedType.LPWStr)] + public string szAccessPath; + [MarshalAs(UnmanagedType.LPWStr)] + public string szItemID; + [MarshalAs(UnmanagedType.I4)] + public int bActive; + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int dwBlobSize; + public IntPtr pBlob; + [MarshalAs(UnmanagedType.I2)] + public short vtRequestedDataType; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + }; + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMATTRIBUTES + { + [MarshalAs(UnmanagedType.LPWStr)] + public string szAccessPath; + [MarshalAs(UnmanagedType.LPWStr)] + public string szItemID; + [MarshalAs(UnmanagedType.I4)] + public int bActive; + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int hServer; + [MarshalAs(UnmanagedType.I4)] + public int dwAccessRights; + [MarshalAs(UnmanagedType.I4)] + public int dwBlobSize; + public IntPtr pBlob; + [MarshalAs(UnmanagedType.I2)] + public short vtRequestedDataType; + [MarshalAs(UnmanagedType.I2)] + public short vtCanonicalDataType; + public OPCEUTYPE dwEUType; + [MarshalAs(UnmanagedType.Struct)] + public object vEUInfo; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMRESULT + { + [MarshalAs(UnmanagedType.I4)] + public int hServer; + [MarshalAs(UnmanagedType.I2)] + public short vtCanonicalDataType; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + [MarshalAs(UnmanagedType.I4)] + public int dwAccessRights; + [MarshalAs(UnmanagedType.I4)] + public int dwBlobSize; + public IntPtr pBlob; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMPROPERTY + { + [MarshalAs(UnmanagedType.I2)] + public short vtDataType; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + [MarshalAs(UnmanagedType.I4)] + public int dwPropertyID; + [MarshalAs(UnmanagedType.LPWStr)] + public string szItemID; + [MarshalAs(UnmanagedType.LPWStr)] + public string szDescription; + [MarshalAs(UnmanagedType.Struct)] + public object vValue; + [MarshalAs(UnmanagedType.I4)] + public int hrErrorID; + [MarshalAs(UnmanagedType.I4)] + public int dwReserved; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMPROPERTIES + { + [MarshalAs(UnmanagedType.I4)] + public int hrErrorID; + [MarshalAs(UnmanagedType.I4)] + public int dwNumProperties; + public IntPtr pItemProperties; + [MarshalAs(UnmanagedType.I4)] + public int dwReserved; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCBROWSEELEMENT + { + [MarshalAs(UnmanagedType.LPWStr)] + public string szName; + [MarshalAs(UnmanagedType.LPWStr)] + public string szItemID; + [MarshalAs(UnmanagedType.I4)] + public int dwFlagValue; + [MarshalAs(UnmanagedType.I4)] + public int dwReserved; + public OPCITEMPROPERTIES ItemProperties; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCITEMVQT + { + [MarshalAs(UnmanagedType.Struct)] + public object vDataValue; + [MarshalAs(UnmanagedType.I4)] + public int bQualitySpecified; + [MarshalAs(UnmanagedType.I2)] + public short wQuality; + [MarshalAs(UnmanagedType.I2)] + public short wReserved; + [MarshalAs(UnmanagedType.I4)] + public int bTimeStampSpecified; + [MarshalAs(UnmanagedType.I4)] + public int dwReserved; + public FILETIME ftTimeStamp; + } + + /// + public enum OPCBROWSEFILTER + { + OPC_BROWSE_FILTER_ALL = 1, + OPC_BROWSE_FILTER_BRANCHES, + OPC_BROWSE_FILTER_ITEMS, + } + + /// + [ComImport] + [GuidAttribute("39c13a4d-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCServer + { + void AddGroup( + [MarshalAs(UnmanagedType.LPWStr)] + string szName, + [MarshalAs(UnmanagedType.I4)] + int bActive, + [MarshalAs(UnmanagedType.I4)] + int dwRequestedUpdateRate, + [MarshalAs(UnmanagedType.I4)] + int hClientGroup, + IntPtr pTimeBias, + IntPtr pPercentDeadband, + [MarshalAs(UnmanagedType.I4)] + int dwLCID, + [Out][MarshalAs(UnmanagedType.I4)] + out int phServerGroup, + [Out][MarshalAs(UnmanagedType.I4)] + out int pRevisedUpdateRate, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=9)] + out object ppUnk); + + void GetErrorString( + [MarshalAs(UnmanagedType.I4)] + int dwError, + [MarshalAs(UnmanagedType.I4)] + int dwLocale, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppString); + + void GetGroupByName( + [MarshalAs(UnmanagedType.LPWStr)] + string szName, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=1)] + out object ppUnk); + + void GetStatus( + [Out] + out IntPtr ppServerStatus); + + void RemoveGroup( + [MarshalAs(UnmanagedType.I4)] + int hServerGroup, + [MarshalAs(UnmanagedType.I4)] + int bForce); + + void CreateGroupEnumerator( + OPCENUMSCOPE dwScope, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=1)] + out object ppUnk); + } + + /// + [ComImport] + [GuidAttribute("39c13a4e-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCServerPublicGroups + { + void GetPublicGroupByName( + [MarshalAs(UnmanagedType.LPWStr)] + string szName, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=1)] + out object ppUnk); + + void RemovePublicGroup( + [MarshalAs(UnmanagedType.I4)] + int hServerGroup, + [MarshalAs(UnmanagedType.I4)] + int bForce); + } + + /// + [ComImport] + [GuidAttribute("39c13a4f-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCBrowseServerAddressSpace + { + void QueryOrganization( + [Out] + out OPCNAMESPACETYPE pNameSpaceType); + + void ChangeBrowsePosition( + OPCBROWSEDIRECTION dwBrowseDirection, + [MarshalAs(UnmanagedType.LPWStr)] + string szString); + + void BrowseOPCItemIDs( + OPCBROWSETYPE dwBrowseFilterType, + [MarshalAs(UnmanagedType.LPWStr)] + string szFilterCriteria, + [MarshalAs(UnmanagedType.I2)] + short vtDataTypeFilter, + [MarshalAs(UnmanagedType.I4)] + int dwAccessRightsFilter, + [Out] + out OpcRcw.Comn.IEnumString ppIEnumString); + + void GetItemID( + [MarshalAs(UnmanagedType.LPWStr)] + string szItemDataID, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string szItemID); + + void BrowseAccessPaths( + [MarshalAs(UnmanagedType.LPWStr)] + string szItemID, + [Out] + out OpcRcw.Comn.IEnumString pIEnumString); + } + + /// + [ComImport] + [GuidAttribute("39c13a50-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCGroupStateMgt + { + void GetState( + [Out][MarshalAs(UnmanagedType.I4)] + out int pUpdateRate, + [Out][MarshalAs(UnmanagedType.I4)] + out int pActive, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppName, + [Out][MarshalAs(UnmanagedType.I4)] + out int pTimeBias, + [Out][MarshalAs(UnmanagedType.R4)] + out float pPercentDeadband, + [Out][MarshalAs(UnmanagedType.I4)] + out int pLCID, + [Out][MarshalAs(UnmanagedType.I4)] + out int phClientGroup, + [Out][MarshalAs(UnmanagedType.I4)] + out int phServerGroup); + + void SetState( + IntPtr pRequestedUpdateRate, + [Out][MarshalAs(UnmanagedType.I4)] + out int pRevisedUpdateRate, + IntPtr pActive, + IntPtr pTimeBias, + IntPtr pPercentDeadband, + IntPtr pLCID, + IntPtr phClientGroup); + + void SetName( + [MarshalAs(UnmanagedType.LPWStr)] + string szName); + + void CloneGroup( + [MarshalAs(UnmanagedType.LPWStr)] + string szName, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=1)] + out object ppUnk); + } + + /// + [ComImport] + [GuidAttribute("39c13a51-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCPublicGroupStateMgt + { + void GetState( + [Out][MarshalAs(UnmanagedType.I4)] + out int pPublic); + + void MoveToPublic(); + } + + /// + [ComImport] + [GuidAttribute("39c13a52-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCSyncIO + { + void Read( + OPCDATASOURCE dwSource, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [Out] + out IntPtr ppItemValues, + [Out] + out IntPtr ppErrors); + + void Write( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] pItemValues, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("39c13a53-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCAsyncIO + { + void Read( + [MarshalAs(UnmanagedType.I4)] + int dwConnection, + OPCDATASOURCE dwSource, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phServer, + [Out][MarshalAs(UnmanagedType.I4)] + out int pTransactionID, + [Out] + out IntPtr ppErrors); + + void Write( + [MarshalAs(UnmanagedType.I4)] + int dwConnection, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=1)] + object[] pItemValues, + [Out][MarshalAs(UnmanagedType.I4)] + out int pTransactionID, + [Out] + out IntPtr ppErrors); + + void Refresh( + [MarshalAs(UnmanagedType.I4)] + int dwConnection, + OPCDATASOURCE dwSource, + [Out][MarshalAs(UnmanagedType.I4)] + out int pTransactionID); + + void Cancel( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID); + } + + /// + [ComImport] + [GuidAttribute("39c13a54-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCItemMgt + { + void AddItems( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCITEMDEF[] pItemArray, + [Out] + out IntPtr ppAddResults, + [Out] + out IntPtr ppErrors); + + void ValidateItems( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCITEMDEF[] pItemArray, + [MarshalAs(UnmanagedType.I4)] + int bBlobUpdate, + [Out] + out IntPtr ppValidationResults, + [Out] + out IntPtr ppErrors); + + void RemoveItems( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [Out] + out IntPtr ppErrors); + + void SetActiveState( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.I4)] + int bActive, + [Out] + out IntPtr ppErrors); + + void SetClientHandles( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phClient, + [Out] + out IntPtr ppErrors); + + void SetDatatypes( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I2, SizeParamIndex=0)] + short[] pRequestedDatatypes, + [Out] + out IntPtr ppErrors); + + void CreateEnumerator( + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=0)] + out object ppUnk); + } + + /// + [ComImport] + [GuidAttribute("39c13a55-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IEnumOPCItemAttributes + { + void Next( + [MarshalAs(UnmanagedType.I4)] + int celt, + [Out] + out IntPtr ppItemArray, + [Out][MarshalAs(UnmanagedType.I4)] + out int pceltFetched); + + void Skip( + [MarshalAs(UnmanagedType.I4)] + int celt); + + void Reset(); + + void Clone( + [Out] + out IEnumOPCItemAttributes ppEnumItemAttributes); + } + + /// + [ComImport] + [GuidAttribute("39c13a70-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCDataCallback + { + void OnDataChange( + [MarshalAs(UnmanagedType.I4)] + int dwTransid, + [MarshalAs(UnmanagedType.I4)] + int hGroup, + [MarshalAs(UnmanagedType.I4)] + int hrMasterquality, + [MarshalAs(UnmanagedType.I4)] + int hrMastererror, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] phClientItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=4)] + object[] pvValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I2, SizeParamIndex=4)] + short[] pwQualities, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=4)] + FILETIME[] pftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] pErrors); + + void OnReadComplete( + [MarshalAs(UnmanagedType.I4)] + int dwTransid, + [MarshalAs(UnmanagedType.I4)] + int hGroup, + [MarshalAs(UnmanagedType.I4)] + int hrMasterquality, + [MarshalAs(UnmanagedType.I4)] + int hrMastererror, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] phClientItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=4)] + object[] pvValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I2, SizeParamIndex=4)] + short[] pwQualities, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=4)] + FILETIME[] pftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] pErrors); + + void OnWriteComplete( + [MarshalAs(UnmanagedType.I4)] + int dwTransid, + [MarshalAs(UnmanagedType.I4)] + int hGroup, + [MarshalAs(UnmanagedType.I4)] + int hrMastererr, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] pClienthandles, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] pErrors); + + void OnCancelComplete( + [MarshalAs(UnmanagedType.I4)] + int dwTransid, + [MarshalAs(UnmanagedType.I4)] + int hGroup); + } + + /// + [ComImport] + [GuidAttribute("39c13a71-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCAsyncIO2 + { + void Read( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Write( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] pItemValues, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Refresh2( + OPCDATASOURCE dwSource, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCancelID); + + void Cancel2( + [MarshalAs(UnmanagedType.I4)] + int dwCancelID); + + void SetEnable( + [MarshalAs(UnmanagedType.I4)] + int bEnable); + + void GetEnable( + [Out][MarshalAs(UnmanagedType.I4)] + out int pbEnable); + } + + /// + [ComImport] + [GuidAttribute("39c13a72-011e-11d0-9675-0020afd8adb3")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCItemProperties + { + void QueryAvailableProperties( + [MarshalAs(UnmanagedType.LPWStr)] + string szItemID, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppPropertyIDs, + [Out] + out IntPtr ppDescriptions, + [Out] + out IntPtr ppvtDataTypes); + + void GetItemProperties( + [MarshalAs(UnmanagedType.LPWStr)] + string szItemID, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] pdwPropertyIDs, + [Out] + out IntPtr ppvData, + [Out] + out IntPtr ppErrors); + + void LookupItemIDs( + [MarshalAs(UnmanagedType.LPWStr)] + string szItemID, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] pdwPropertyIDs, + [Out] + out IntPtr ppszNewItemIDs, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("5946DA93-8B39-4ec8-AB3D-AA73DF5BC86F")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCItemDeadbandMgt + { + void SetItemDeadband( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.R4, SizeParamIndex=0)] + float[] pPercentDeadband, + [Out] + out IntPtr ppErrors); + + void GetItemDeadband( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [Out] + out IntPtr ppPercentDeadband, + [Out] + out IntPtr ppErrors); + + void ClearItemDeadband( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("3E22D313-F08B-41a5-86C8-95E95CB49FFC")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCItemSamplingMgt + { + void SetItemSamplingRate( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwRequestedSamplingRate, + [Out] + out IntPtr ppdwRevisedSamplingRate, + [Out] + out IntPtr ppErrors); + + void GetItemSamplingRate( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [Out] + out IntPtr ppdwSamplingRate, + [Out] + out IntPtr ppErrors); + + void ClearItemSamplingRate( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [Out] + out IntPtr ppErrors); + + void SetItemBufferEnable( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pbEnable, + [Out] + out IntPtr ppErrors); + + void GetItemBufferEnable( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [Out] + out IntPtr ppbEnable, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("39227004-A18F-4b57-8B0A-5235670F4468")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCBrowse + { + void GetProperties( + [MarshalAs(UnmanagedType.I4)] + int dwItemCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszItemIDs, + [MarshalAs(UnmanagedType.I4)] + int bReturnPropertyValues, + [MarshalAs(UnmanagedType.I4)] + int dwPropertyCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] dwPropertyIDs, + [Out] + out IntPtr ppItemProperties); + + void Browse( + [MarshalAs(UnmanagedType.LPWStr)] + string szItemID, + ref IntPtr pszContinuationPoint, + [MarshalAs(UnmanagedType.I4)] + int dwMaxElementsReturned, + OPCBROWSEFILTER dwBrowseFilter, + [MarshalAs(UnmanagedType.LPWStr)] + string szElementNameFilter, + [MarshalAs(UnmanagedType.LPWStr)] + string szVendorFilter, + [MarshalAs(UnmanagedType.I4)] + int bReturnAllProperties, + [MarshalAs(UnmanagedType.I4)] + int bReturnPropertyValues, + [MarshalAs(UnmanagedType.I4)] + int dwPropertyCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=8)] + int[] pdwPropertyIDs, + [Out][MarshalAs(UnmanagedType.I4)] + out int pbMoreElements, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppBrowseElements); + } + + /// + [ComImport] + [GuidAttribute("85C0B427-2893-4cbc-BD78-E5FC5146F08F")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCItemIO + { + void Read( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszItemIDs, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwMaxAge, + [Out] + out IntPtr ppvValues, + [Out] + out IntPtr ppwQualities, + [Out] + out IntPtr ppftTimeStamps, + [Out] + out IntPtr ppErrors); + + void WriteVQT( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszItemIDs, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCITEMVQT[] pItemVQT, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("730F5F0F-55B1-4c81-9E18-FF8A0904E1FA")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCSyncIO2 // : IOPCSyncIO + { + void Read( + OPCDATASOURCE dwSource, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [Out] + out IntPtr ppItemValues, + [Out] + out IntPtr ppErrors); + + void Write( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] pItemValues, + [Out] + out IntPtr ppErrors); + + void ReadMaxAge( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwMaxAge, + [Out] + out IntPtr ppvValues, + [Out] + out IntPtr ppwQualities, + [Out] + out IntPtr ppftTimeStamps, + [Out] + out IntPtr ppErrors); + + void WriteVQT( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCITEMVQT[] pItemVQT, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("0967B97B-36EF-423e-B6F8-6BFF1E40D39D")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCAsyncIO3 // : IOPCAsyncIO2 + { + void Read( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Write( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] pItemValues, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Refresh2( + OPCDATASOURCE dwSource, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCancelID); + + void Cancel2( + [MarshalAs(UnmanagedType.I4)] + int dwCancelID); + + void SetEnable( + [MarshalAs(UnmanagedType.I4)] + int bEnable); + + void GetEnable( + [Out][MarshalAs(UnmanagedType.I4)] + out int pbEnable); + + void ReadMaxAge( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwMaxAge, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out] + [MarshalAs(UnmanagedType.I4)] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void WriteVQT( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCITEMVQT[] pItemVQT, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out] + [MarshalAs(UnmanagedType.I4)] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void RefreshMaxAge( + [MarshalAs(UnmanagedType.I4)] + int dwMaxAge, + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [Out] + [MarshalAs(UnmanagedType.I4)] + out int pdwCancelID); + } + + /// + [ComImport] + [GuidAttribute("8E368666-D72E-4f78-87ED-647611C61C9F")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCGroupStateMgt2 // : IOPCGroupStateMgt + { + void GetState( + [Out][MarshalAs(UnmanagedType.I4)] + out int pUpdateRate, + [Out][MarshalAs(UnmanagedType.I4)] + out int pActive, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppName, + [Out][MarshalAs(UnmanagedType.I4)] + out int pTimeBias, + [Out][MarshalAs(UnmanagedType.R4)] + out float pPercentDeadband, + [Out][MarshalAs(UnmanagedType.I4)] + out int pLCID, + [Out][MarshalAs(UnmanagedType.I4)] + out int phClientGroup, + [Out][MarshalAs(UnmanagedType.I4)] + out int phServerGroup); + + void SetState( + IntPtr pRequestedUpdateRate, + [Out][MarshalAs(UnmanagedType.I4)] + out int pRevisedUpdateRate, + IntPtr pActive, + IntPtr pTimeBias, + IntPtr pPercentDeadband, + IntPtr pLCID, + IntPtr phClientGroup); + + void SetName( + [MarshalAs(UnmanagedType.LPWStr)] + string szName); + + void CloneGroup( + [MarshalAs(UnmanagedType.LPWStr)] + string szName, + ref Guid riid, + [Out][MarshalAs(UnmanagedType.IUnknown, IidParameterIndex=1)] + out object ppUnk); + + void SetKeepAlive( + [MarshalAs(UnmanagedType.I4)] + int dwKeepAliveTime, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwRevisedKeepAliveTime); + + void GetKeepAlive( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwKeepAliveTime); + } + + /// + public static class Constants + { + // category description strings. + public const string OPC_CATEGORY_DESCRIPTION_DA10 = "OPC Data Access Servers Version 1.0"; + public const string OPC_CATEGORY_DESCRIPTION_DA20 = "OPC Data Access Servers Version 2.0"; + public const string OPC_CATEGORY_DESCRIPTION_DA30 = "OPC Data Access Servers Version 3.0"; + public const string OPC_CATEGORY_DESCRIPTION_XMLDA10 = "OPC XML Data Access Servers Version 1.0"; + + // values for access rights mask. + public const int OPC_READABLE = 0x01; + public const int OPC_WRITEABLE = 0x02; + + // values for browse element flags. + public const int OPC_BROWSE_HASCHILDREN = 0x01; + public const int OPC_BROWSE_ISITEM = 0x02; + + // well known complex type description systems. + public const string OPC_TYPE_SYSTEM_OPCBINARY = "OPCBinary"; + public const string OPC_TYPE_SYSTEM_XMLSCHEMA = "XMLSchema"; + + // complex data consitency window values. + public const string OPC_CONSISTENCY_WINDOW_UNKNOWN = "Unknown"; + public const string OPC_CONSISTENCY_WINDOW_NOT_CONSISTENT = "Not Consistent"; + + // complex data write behavoir values. + public const string OPC_WRITE_BEHAVIOR_BEST_EFFORT = "Best Effort"; + public const string OPC_WRITE_BEHAVIOR_ALL_OR_NOTHING = "All or Nothing"; + } + + /// + public static class Qualities + { + // Values for fields in the quality word + public const short OPC_QUALITY_MASK = 0xC0; + public const short OPC_STATUS_MASK = 0xFC; + public const short OPC_LIMIT_MASK = 0x03; + + // Values for QUALITY_MASK bit field + public const short OPC_QUALITY_BAD = 0x00; + public const short OPC_QUALITY_UNCERTAIN = 0x40; + public const short OPC_QUALITY_GOOD = 0xC0; + + // STATUS_MASK Values for Quality = BAD + public const short OPC_QUALITY_CONFIG_ERROR = 0x04; + public const short OPC_QUALITY_NOT_CONNECTED = 0x08; + public const short OPC_QUALITY_DEVICE_FAILURE = 0x0c; + public const short OPC_QUALITY_SENSOR_FAILURE = 0x10; + public const short OPC_QUALITY_LAST_KNOWN = 0x14; + public const short OPC_QUALITY_COMM_FAILURE = 0x18; + public const short OPC_QUALITY_OUT_OF_SERVICE = 0x1C; + public const short OPC_QUALITY_WAITING_FOR_INITIAL_DATA = 0x20; + + // STATUS_MASK Values for Quality = UNCERTAIN + public const short OPC_QUALITY_LAST_USABLE = 0x44; + public const short OPC_QUALITY_SENSOR_CAL = 0x50; + public const short OPC_QUALITY_EGU_EXCEEDED = 0x54; + public const short OPC_QUALITY_SUB_NORMAL = 0x58; + + // STATUS_MASK Values for Quality = GOOD + public const short OPC_QUALITY_LOCAL_OVERRIDE = 0xD8; + + // Values for Limit Bitfield + public const short OPC_LIMIT_OK = 0x00; + public const short OPC_LIMIT_LOW = 0x01; + public const short OPC_LIMIT_HIGH = 0x02; + public const short OPC_LIMIT_CONST = 0x03; + } + + //========================================================================== + // Properties + + /// + public static class Properties + { + // property ids. + public const int OPC_PROPERTY_DATATYPE = 1; + public const int OPC_PROPERTY_VALUE = 2; + public const int OPC_PROPERTY_QUALITY = 3; + public const int OPC_PROPERTY_TIMESTAMP = 4; + public const int OPC_PROPERTY_ACCESS_RIGHTS = 5; + public const int OPC_PROPERTY_SCAN_RATE = 6; + public const int OPC_PROPERTY_EU_TYPE = 7; + public const int OPC_PROPERTY_EU_INFO = 8; + public const int OPC_PROPERTY_EU_UNITS = 100; + public const int OPC_PROPERTY_DESCRIPTION = 101; + public const int OPC_PROPERTY_HIGH_EU = 102; + public const int OPC_PROPERTY_LOW_EU = 103; + public const int OPC_PROPERTY_HIGH_IR = 104; + public const int OPC_PROPERTY_LOW_IR = 105; + public const int OPC_PROPERTY_CLOSE_LABEL = 106; + public const int OPC_PROPERTY_OPEN_LABEL = 107; + public const int OPC_PROPERTY_TIMEZONE = 108; + public const int OPC_PROPERTY_CONDITION_STATUS = 300; + public const int OPC_PROPERTY_ALARM_QUICK_HELP = 301; + public const int OPC_PROPERTY_ALARM_AREA_LIST = 302; + public const int OPC_PROPERTY_PRIMARY_ALARM_AREA = 303; + public const int OPC_PROPERTY_CONDITION_LOGIC = 304; + public const int OPC_PROPERTY_LIMIT_EXCEEDED = 305; + public const int OPC_PROPERTY_DEADBAND = 306; + public const int OPC_PROPERTY_HIHI_LIMIT = 307; + public const int OPC_PROPERTY_HI_LIMIT = 308; + public const int OPC_PROPERTY_LO_LIMIT = 309; + public const int OPC_PROPERTY_LOLO_LIMIT = 310; + public const int OPC_PROPERTY_CHANGE_RATE_LIMIT = 311; + public const int OPC_PROPERTY_DEVIATION_LIMIT = 312; + public const int OPC_PROPERTY_SOUND_FILE = 313; + + // complex data properties. + public const int OPC_PROPERTY_TYPE_SYSTEM_ID = 600; + public const int OPC_PROPERTY_DICTIONARY_ID = 601; + public const int OPC_PROPERTY_TYPE_ID = 602; + public const int OPC_PROPERTY_DICTIONARY = 603; + public const int OPC_PROPERTY_TYPE_DESCRIPTION = 604; + public const int OPC_PROPERTY_CONSISTENCY_WINDOW = 605; + public const int OPC_PROPERTY_WRITE_BEHAVIOR = 606; + public const int OPC_PROPERTY_UNCONVERTED_ITEM_ID = 607; + public const int OPC_PROPERTY_UNFILTERED_ITEM_ID = 608; + public const int OPC_PROPERTY_DATA_FILTER_VALUE = 609; + + // property descriptions. + public const string OPC_PROPERTY_DESC_DATATYPE = "Item Canonical Data Type"; + public const string OPC_PROPERTY_DESC_VALUE = "Item Value"; + public const string OPC_PROPERTY_DESC_QUALITY = "Item Quality"; + public const string OPC_PROPERTY_DESC_TIMESTAMP = "Item Timestamp"; + public const string OPC_PROPERTY_DESC_ACCESS_RIGHTS = "Item Access Rights"; + public const string OPC_PROPERTY_DESC_SCAN_RATE = "Server Scan Rate"; + public const string OPC_PROPERTY_DESC_EU_TYPE = "Item EU Type"; + public const string OPC_PROPERTY_DESC_EU_INFO = "Item EU Info"; + public const string OPC_PROPERTY_DESC_EU_UNITS = "EU Units"; + public const string OPC_PROPERTY_DESC_DESCRIPTION = "Item Description"; + public const string OPC_PROPERTY_DESC_HIGH_EU = "High EU"; + public const string OPC_PROPERTY_DESC_LOW_EU = "Low EU"; + public const string OPC_PROPERTY_DESC_HIGH_IR = "High Instrument Range"; + public const string OPC_PROPERTY_DESC_LOW_IR = "Low Instrument Range"; + public const string OPC_PROPERTY_DESC_CLOSE_LABEL = "Contact Close Label"; + public const string OPC_PROPERTY_DESC_OPEN_LABEL = "Contact Open Label"; + public const string OPC_PROPERTY_DESC_TIMEZONE = "Item Timezone"; + public const string OPC_PROPERTY_DESC_CONDITION_STATUS = "Condition Status"; + public const string OPC_PROPERTY_DESC_ALARM_QUICK_HELP = "Alarm Quick Help"; + public const string OPC_PROPERTY_DESC_ALARM_AREA_LIST = "Alarm Area List"; + public const string OPC_PROPERTY_DESC_PRIMARY_ALARM_AREA = "Primary Alarm Area"; + public const string OPC_PROPERTY_DESC_CONDITION_LOGIC = "Condition Logic"; + public const string OPC_PROPERTY_DESC_LIMIT_EXCEEDED = "Limit Exceeded"; + public const string OPC_PROPERTY_DESC_DEADBAND = "Deadband"; + public const string OPC_PROPERTY_DESC_HIHI_LIMIT = "HiHi Limit"; + public const string OPC_PROPERTY_DESC_HI_LIMIT = "Hi Limit"; + public const string OPC_PROPERTY_DESC_LO_LIMIT = "Lo Limit"; + public const string OPC_PROPERTY_DESC_LOLO_LIMIT = "LoLo Limit"; + public const string OPC_PROPERTY_DESC_CHANGE_RATE_LIMIT = "Rate of Change Limit"; + public const string OPC_PROPERTY_DESC_DEVIATION_LIMIT = "Deviation Limit"; + public const string OPC_PROPERTY_DESC_SOUND_FILE = "Sound File"; + + // complex data properties. + public const string OPC_PROPERTY_DESC_TYPE_SYSTEM_ID = "Type System ID"; + public const string OPC_PROPERTY_DESC_DICTIONARY_ID = "Dictionary ID"; + public const string OPC_PROPERTY_DESC_TYPE_ID = "Type ID"; + public const string OPC_PROPERTY_DESC_DICTIONARY = "Dictionary"; + public const string OPC_PROPERTY_DESC_TYPE_DESCRIPTION = "Type Description"; + public const string OPC_PROPERTY_DESC_CONSISTENCY_WINDOW = "Consistency Window"; + public const string OPC_PROPERTY_DESC_WRITE_BEHAVIOR = "Write Behavior"; + public const string OPC_PROPERTY_DESC_UNCONVERTED_ITEM_ID = "Unconverted Item ID"; + public const string OPC_PROPERTY_DESC_UNFILTERED_ITEM_ID = "Unfiltered Item ID"; + public const string OPC_PROPERTY_DESC_DATA_FILTER_VALUE = "Data Filter Value"; + } +} diff --git a/Technosoftware/OpcRcw/Hda/HistoricalDataAccess.cs b/Technosoftware/OpcRcw/Hda/HistoricalDataAccess.cs new file mode 100644 index 0000000..6a403d7 --- /dev/null +++ b/Technosoftware/OpcRcw/Hda/HistoricalDataAccess.cs @@ -0,0 +1,1014 @@ +#region Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +//----------------------------------------------------------------------------- +// Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +// Web: https://technosoftware.com +// +// The Software is based on the OPC Foundation MIT License. +// The complete license agreement for that can be found here: +// http://opcfoundation.org/License/MIT/1.00/ +//----------------------------------------------------------------------------- +#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved + +#region Using Directives +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +#endregion + +#pragma warning disable 1591, CS0618 + +namespace Technosoftware.OpcRcw.Hda +{ + + /// + [ComImport] + [GuidAttribute("7DE5B060-E089-11d2-A5E6-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface CATID_OPCHDAServer10 {} + + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct OPCHDA_FILETIME + { + public int dwLowDateTime; + public int dwHighDateTime; + } + + /// + public enum OPCHDA_SERVERSTATUS + { + OPCHDA_UP = 1, + OPCHDA_DOWN, + OPCHDA_INDETERMINATE + } + + /// + public enum OPCHDA_BROWSEDIRECTION + { + OPCHDA_BROWSE_UP = 1, + OPCHDA_BROWSE_DOWN, + OPCHDA_BROWSE_DIRECT + } + + /// + public enum OPCHDA_BROWSETYPE + { + OPCHDA_BRANCH = 1, + OPCHDA_LEAF, + OPCHDA_FLAT, + OPCHDA_ITEMS + } + + /// + public enum OPCHDA_ANNOTATIONCAPABILITIES + { + OPCHDA_READANNOTATIONCAP = 0x01, + OPCHDA_INSERTANNOTATIONCAP = 0x02 + } + + /// + public enum OPCHDA_UPDATECAPABILITIES + { + OPCHDA_INSERTCAP = 0x01, + OPCHDA_REPLACECAP = 0x02, + OPCHDA_INSERTREPLACECAP = 0x04, + OPCHDA_DELETERAWCAP = 0x08, + OPCHDA_DELETEATTIMECAP = 0x10 + } + + /// + public enum OPCHDA_OPERATORCODES + { + OPCHDA_EQUAL = 1, + OPCHDA_LESS, + OPCHDA_LESSEQUAL, + OPCHDA_GREATER, + OPCHDA_GREATEREQUAL, + OPCHDA_NOTEQUAL + } + + /// + public enum OPCHDA_EDITTYPE + { + OPCHDA_INSERT = 1, + OPCHDA_REPLACE, + OPCHDA_INSERTREPLACE, + OPCHDA_DELETE + } + + /// + public enum OPCHDA_AGGREGATE + { + OPCHDA_NOAGGREGATE = 0, + OPCHDA_INTERPOLATIVE, + OPCHDA_TOTAL, + OPCHDA_AVERAGE, + OPCHDA_TIMEAVERAGE, + OPCHDA_COUNT, + OPCHDA_STDEV, + OPCHDA_MINIMUMACTUALTIME, + OPCHDA_MINIMUM, + OPCHDA_MAXIMUMACTUALTIME, + OPCHDA_MAXIMUM, + OPCHDA_START, + OPCHDA_END, + OPCHDA_DELTA, + OPCHDA_REGSLOPE, + OPCHDA_REGCONST, + OPCHDA_REGDEV, + OPCHDA_VARIANCE, + OPCHDA_RANGE, + OPCHDA_DURATIONGOOD, + OPCHDA_DURATIONBAD, + OPCHDA_PERCENTGOOD, + OPCHDA_PERCENTBAD, + OPCHDA_WORSTQUALITY, + OPCHDA_ANNOTATIONS + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCHDA_ANNOTATION + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int dwNumValues; + public IntPtr ftTimeStamps; + public IntPtr szAnnotation; + public IntPtr ftAnnotationTime; + public IntPtr szUser; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCHDA_MODIFIEDITEM + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int dwCount; + public IntPtr pftTimeStamps; + public IntPtr pdwQualities; + public IntPtr pvDataValues; + public IntPtr pftModificationTime; + public IntPtr pEditType; + public IntPtr szUser; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCHDA_ATTRIBUTE + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int dwNumValues; + [MarshalAs(UnmanagedType.I4)] + public int dwAttributeID; + public IntPtr ftTimeStamps; + public IntPtr vAttributeValues; + }; + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCHDA_TIME + { + [MarshalAs(UnmanagedType.I4)] + public int bString; + [MarshalAs(UnmanagedType.LPWStr)] + public string szTime; + public OPCHDA_FILETIME ftTime; + } + + /// + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct OPCHDA_ITEM + { + [MarshalAs(UnmanagedType.I4)] + public int hClient; + [MarshalAs(UnmanagedType.I4)] + public int haAggregate; + [MarshalAs(UnmanagedType.I4)] + public int dwCount; + public IntPtr pftTimeStamps; + public IntPtr pdwQualities; + public IntPtr pvDataValues; + } + + /// + [ComImport] + [GuidAttribute("1F1217B1-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_Browser + { + void GetEnum( + OPCHDA_BROWSETYPE dwBrowseType, + [Out] + out OpcRcw.Comn.IEnumString ppIEnumString); + + void ChangeBrowsePosition( + OPCHDA_BROWSEDIRECTION dwBrowseDirection, + [MarshalAs(UnmanagedType.LPWStr)] + string szString); + + void GetItemID( + [MarshalAs(UnmanagedType.LPWStr)] + string szNode, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string pszItemID); + + void GetBranchPosition( + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string pszBranchPos); + } + + /// + [ComImport] + [GuidAttribute("1F1217B0-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_Server + { + void GetItemAttributes( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwAttrID, + [Out] + out IntPtr ppszAttrName, + [Out] + out IntPtr ppszAttrDesc, + [Out] + out IntPtr ppvtAttrDataType); + + void GetAggregates( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwCount, + [Out] + out IntPtr ppdwAggrID, + [Out] + out IntPtr ppszAggrName, + [Out] + out IntPtr ppszAggrDesc); + + void GetHistorianStatus( + [Out] + out OPCHDA_SERVERSTATUS pwStatus, + [Out] + out IntPtr pftCurrentTime, + [Out] + out IntPtr pftStartTime, + [Out][MarshalAs(UnmanagedType.I2)] + out short pwMajorVersion, + [Out][MarshalAs(UnmanagedType.I2)] + out short wMinorVersion, + [Out][MarshalAs(UnmanagedType.I2)] + out short pwBuildNumber, + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwMaxReturnValues, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppszStatusString, + [Out][MarshalAs(UnmanagedType.LPWStr)] + out string ppszVendorInfo); + + void GetItemHandles( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszItemID, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phClient, + [Out] + out IntPtr pphServer, + [Out] + out IntPtr ppErrors); + + void ReleaseItemHandles( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [Out] + out IntPtr ppErrors); + + void ValidateItemIDs( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] + string[] pszItemID, + [Out] + out IntPtr ppErrors); + + void CreateBrowse( + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwAttrID, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] + OPCHDA_OPERATORCODES[] pOperator, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] vFilter, + out IOPCHDA_Browser pphBrowser, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("1F1217B2-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_SyncRead + { + void ReadRaw( + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumValues, + [MarshalAs(UnmanagedType.I4)] + int bBounds, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] phServer, + [Out] + out IntPtr ppItemValues, + [Out] + out IntPtr ppErrors); + + void ReadProcessed( + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + OPCHDA_FILETIME ftResampleInterval, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] haAggregate, + [Out] + out IntPtr ppItemValues, + [Out] + out IntPtr ppErrors); + + void ReadAtTime( + [MarshalAs(UnmanagedType.I4)] + int dwNumTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phServer, + [Out] + out IntPtr ppItemValues, + [Out] + out IntPtr ppErrors); + + void ReadModified( + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumValues, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] phServer, + [Out] + out IntPtr ppItemValues, + [Out] + out IntPtr ppErrors); + + void ReadAttribute( + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int hServer, + [MarshalAs(UnmanagedType.I4)] + int dwNumAttributes, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] pdwAttributeIDs, + [Out] + out IntPtr ppAttributeValues, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("1F1217B3-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_SyncUpdate + { + void QueryCapabilities( + [Out] + out OPCHDA_UPDATECAPABILITIES pCapabilities); + + void Insert( + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] vDataValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwQualities, + [Out] + out IntPtr ppErrors); + + void Replace( + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] vDataValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwQualities, + [Out] + out IntPtr ppErrors); + + void InsertReplace( + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=0)] + object[] vDataValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] pdwQualities, + [Out] + out IntPtr ppErrors); + + void DeleteRaw( + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phServer, + [Out] + out IntPtr ppErrors); + + void DeleteAtTime( + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCHDA_FILETIME[] ftTimeStamps, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("1F1217B4-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_SyncAnnotations + { + void QueryCapabilities( + [Out] + out OPCHDA_ANNOTATIONCAPABILITIES pCapabilities); + + void Read( + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phServer, + [Out] + out IntPtr ppAnnotationValues, + [Out] + out IntPtr ppErrors); + + void Insert( + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=0)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=0)] + OPCHDA_ANNOTATION[] pAnnotationValues, + [Out] + out IntPtr ppErrors); + } + + /// + [ComImport] + [GuidAttribute("1F1217B5-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_AsyncRead + { + void ReadRaw( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumValues, + [MarshalAs(UnmanagedType.I4)] + int bBounds, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=5)] + int[] phServer, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void AdviseRaw( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + OPCHDA_FILETIME ftUpdateInterval, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] phServer, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void ReadProcessed( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + OPCHDA_FILETIME ftResampleInterval, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] haAggregate, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void AdviseProcessed( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + OPCHDA_FILETIME ftResampleInterval, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] haAggregate, + [MarshalAs(UnmanagedType.I4)] + int dwNumIntervals, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void ReadAtTime( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int dwNumTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=1)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] phServer, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void ReadModified( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumValues, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] phServer, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void ReadAttribute( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int hServer, + [MarshalAs(UnmanagedType.I4)] + int dwNumAttributes, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=4)] + int[] dwAttributeIDs, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Cancel( + [MarshalAs(UnmanagedType.I4)] + int dwCancelID); + } + + /// + [ComImport] + [GuidAttribute("1F1217B6-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_AsyncUpdate + { + void QueryCapabilities( + out OPCHDA_UPDATECAPABILITIES pCapabilities + ); + + void Insert( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=1)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=1)] + object[] vDataValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] pdwQualities, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Replace( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=1)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=1)] + object[] vDataValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] pdwQualities, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void InsertReplace( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=1)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct, SizeParamIndex=1)] + object[] vDataValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] pdwQualities, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void DeleteRaw( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] phServer, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void DeleteAtTime( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=1)] + OPCHDA_FILETIME[] ftTimeStamps, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Cancel( + [MarshalAs(UnmanagedType.I4)] + int dwCancelID); + } + + /// + [ComImport] + [GuidAttribute("1F1217B7-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_AsyncAnnotations + { + void QueryCapabilities( + out OPCHDA_ANNOTATIONCAPABILITIES pCapabilities); + + void Read( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] phServer, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Insert( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=1)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=1)] + OPCHDA_FILETIME[] ftTimeStamps, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=1)] + OPCHDA_ANNOTATION[] pAnnotationValues, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Cancel( + [MarshalAs(UnmanagedType.I4)] + int dwCancelID); + } + + /// + [ComImport] + [GuidAttribute("1F1217B8-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_Playback + { + void ReadRawWithUpdate( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + [MarshalAs(UnmanagedType.I4)] + int dwNumValues, + OPCHDA_FILETIME ftUpdateDuration, + OPCHDA_FILETIME ftUpdateInterval, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=6)] + int[] phServer, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void ReadProcessedWithUpdate( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + ref OPCHDA_TIME htStartTime, + ref OPCHDA_TIME htEndTime, + OPCHDA_FILETIME ftResampleInterval, + [MarshalAs(UnmanagedType.I4)] + int dwNumIntervals, + OPCHDA_FILETIME ftUpdateInterval, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=6)] + int[] phServer, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=6)] + int[] haAggregate, + [Out] + out int pdwCancelID, + [Out] + out IntPtr ppErrors); + + void Cancel( + [MarshalAs(UnmanagedType.I4)] + int dwCancelID); + } + + /// + [ComImport] + [GuidAttribute("1F1217B9-DEE0-11d2-A5E5-000086339399")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCHDA_DataCallback + { + void OnDataChange( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=2)] + OPCHDA_ITEM[] pItemValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phrErrors); + + void OnReadComplete( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=2)] + OPCHDA_ITEM[] pItemValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phrErrors); + + void OnReadModifiedComplete( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=2)] + OPCHDA_MODIFIEDITEM[] pItemValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phrErrors); + + void OnReadAttributeComplete( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int hClient, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=3)] + OPCHDA_ATTRIBUTE[] pAttributeValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=3)] + int[] phrErrors); + + void OnReadAnnotations( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStruct, SizeParamIndex=2)] + OPCHDA_ANNOTATION[] pAnnotationValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phrErrors); + + void OnInsertAnnotations ( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phClients, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phrErrors); + + void OnPlayback ( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int dwNumItems, + IntPtr ppItemValues, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phrErrors); + + void OnUpdateComplete ( + [MarshalAs(UnmanagedType.I4)] + int dwTransactionID, + [MarshalAs(UnmanagedType.I4)] + int hrStatus, + [MarshalAs(UnmanagedType.I4)] + int dwCount, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phClients, + [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I4, SizeParamIndex=2)] + int[] phrErrors); + + void OnCancelComplete( + [MarshalAs(UnmanagedType.I4)] + int dwCancelID); + } + + /// + public static class Constants + { + // category description. + public const string OPC_CATEGORY_DESCRIPTION_HDA10 = "OPC History Data Access Servers Version 1.0"; + + // attribute ids. + public const int OPCHDA_DATA_TYPE = 0x01; + public const int OPCHDA_DESCRIPTION = 0x02; + public const int OPCHDA_ENG_UNITS = 0x03; + public const int OPCHDA_STEPPED = 0x04; + public const int OPCHDA_ARCHIVING = 0x05; + public const int OPCHDA_DERIVE_EQUATION = 0x06; + public const int OPCHDA_NODE_NAME = 0x07; + public const int OPCHDA_PROCESS_NAME = 0x08; + public const int OPCHDA_SOURCE_NAME = 0x09; + public const int OPCHDA_SOURCE_TYPE = 0x0a; + public const int OPCHDA_NORMAL_MAXIMUM = 0x0b; + public const int OPCHDA_NORMAL_MINIMUM = 0x0c; + public const int OPCHDA_ITEMID = 0x0d; + public const int OPCHDA_MAX_TIME_INT = 0x0e; + public const int OPCHDA_MIN_TIME_INT = 0x0f; + public const int OPCHDA_EXCEPTION_DEV = 0x10; + public const int OPCHDA_EXCEPTION_DEV_TYPE = 0x11; + public const int OPCHDA_HIGH_ENTRY_LIMIT = 0x12; + public const int OPCHDA_LOW_ENTRY_LIMIT = 0x13; + + // attribute names. + public const string OPCHDA_ATTRNAME_DATA_TYPE = "Data Type"; + public const string OPCHDA_ATTRNAME_DESCRIPTION = "Description"; + public const string OPCHDA_ATTRNAME_ENG_UNITS = "Eng Units"; + public const string OPCHDA_ATTRNAME_STEPPED = "Stepped"; + public const string OPCHDA_ATTRNAME_ARCHIVING = "Archiving"; + public const string OPCHDA_ATTRNAME_DERIVE_EQUATION = "Derive Equation"; + public const string OPCHDA_ATTRNAME_NODE_NAME = "Node Name"; + public const string OPCHDA_ATTRNAME_PROCESS_NAME = "Process Name"; + public const string OPCHDA_ATTRNAME_SOURCE_NAME = "Source Name"; + public const string OPCHDA_ATTRNAME_SOURCE_TYPE = "Source Type"; + public const string OPCHDA_ATTRNAME_NORMAL_MAXIMUM = "Normal Maximum"; + public const string OPCHDA_ATTRNAME_NORMAL_MINIMUM = "Normal Minimum"; + public const string OPCHDA_ATTRNAME_ITEMID = "ItemID"; + public const string OPCHDA_ATTRNAME_MAX_TIME_INT = "Max Time Interval"; + public const string OPCHDA_ATTRNAME_MIN_TIME_INT = "Min Time Interval"; + public const string OPCHDA_ATTRNAME_EXCEPTION_DEV = "Exception Deviation"; + public const string OPCHDA_ATTRNAME_EXCEPTION_DEV_TYPE = "Exception Dev Type"; + public const string OPCHDA_ATTRNAME_HIGH_ENTRY_LIMIT = "High Entry Limit"; + public const string OPCHDA_ATTRNAME_LOW_ENTRY_LIMIT = "Low Entry Limit"; + + // aggregate names. + public const string OPCHDA_AGGRNAME_INTERPOLATIVE = "Interpolative"; + public const string OPCHDA_AGGRNAME_TOTAL = "Total"; + public const string OPCHDA_AGGRNAME_AVERAGE = "Average"; + public const string OPCHDA_AGGRNAME_TIMEAVERAGE = "Time Average"; + public const string OPCHDA_AGGRNAME_COUNT = "Count"; + public const string OPCHDA_AGGRNAME_STDEV = "Standard Deviation"; + public const string OPCHDA_AGGRNAME_MINIMUMACTUALTIME = "Minimum Actual Time"; + public const string OPCHDA_AGGRNAME_MINIMUM = "Minimum"; + public const string OPCHDA_AGGRNAME_MAXIMUMACTUALTIME = "Maximum Actual Time"; + public const string OPCHDA_AGGRNAME_MAXIMUM = "Maximum"; + public const string OPCHDA_AGGRNAME_START = "Start"; + public const string OPCHDA_AGGRNAME_END = "End"; + public const string OPCHDA_AGGRNAME_DELTA = "Delta"; + public const string OPCHDA_AGGRNAME_REGSLOPE = "Regression Line Slope"; + public const string OPCHDA_AGGRNAME_REGCONST = "Regression Line Constant"; + public const string OPCHDA_AGGRNAME_REGDEV = "Regression Line Error"; + public const string OPCHDA_AGGRNAME_VARIANCE = "Variance"; + public const string OPCHDA_AGGRNAME_RANGE = "Range"; + public const string OPCHDA_AGGRNAME_DURATIONGOOD = "Duration Good"; + public const string OPCHDA_AGGRNAME_DURATIONBAD = "Duration Bad"; + public const string OPCHDA_AGGRNAME_PERCENTGOOD = "Percent Good"; + public const string OPCHDA_AGGRNAME_PERCENTBAD = "Percent Bad"; + public const string OPCHDA_AGGRNAME_WORSTQUALITY = "Worst Quality"; + public const string OPCHDA_AGGRNAME_ANNOTATIONS = "Annotations"; + + // OPCHDA_QUALITY -- these are the high-order 16 bits, OPC DA Quality occupies low-order 16 bits. + public const int OPCHDA_EXTRADATA = 0x00010000; + public const int OPCHDA_INTERPOLATED = 0x00020000; + public const int OPCHDA_RAW = 0x00040000; + public const int OPCHDA_CALCULATED = 0x00080000; + public const int OPCHDA_NOBOUND = 0x00100000; + public const int OPCHDA_NODATA = 0x00200000; + public const int OPCHDA_DATALOST = 0x00400000; + public const int OPCHDA_CONVERSION = 0x00800000; + public const int OPCHDA_PARTIAL = 0x01000000; + } +} diff --git a/Technosoftware/OpcRcw/Security/Security.cs b/Technosoftware/OpcRcw/Security/Security.cs new file mode 100644 index 0000000..fe66aa9 --- /dev/null +++ b/Technosoftware/OpcRcw/Security/Security.cs @@ -0,0 +1,56 @@ +#region Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +//----------------------------------------------------------------------------- +// Copyright (c) 2022-2023 Technosoftware GmbH. All rights reserved +// Web: https://technosoftware.com +// +// The Software is based on the OPC Foundation MIT License. +// The complete license agreement for that can be found here: +// http://opcfoundation.org/License/MIT/1.00/ +//----------------------------------------------------------------------------- +#endregion Copyright (c) 2011-2023 Technosoftware GmbH. All rights reserved + +#region Using Directives + +using System.Runtime.InteropServices; +#endregion + +#pragma warning disable 1591 + +namespace Technosoftware.OpcRcw.Security +{ + /// + [ComImport] + [GuidAttribute("7AA83A01-6C77-11d3-84F9-00008630A38B")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCSecurityNT + { + void IsAvailableNT( + [Out][MarshalAs(UnmanagedType.I4)] + out int pbAvailable); + + void QueryMinImpersonationLevel( + [Out][MarshalAs(UnmanagedType.I4)] + out int pdwMinImpLevel); + + void ChangeUser(); + }; + + /// + [ComImport] + [GuidAttribute("7AA83A02-6C77-11d3-84F9-00008630A38B")] + [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOPCSecurityPrivate + { + void IsAvailablePriv( + [Out][MarshalAs(UnmanagedType.I4)] + out int pbAvailable); + + void Logon( + [MarshalAs(UnmanagedType.LPWStr)] + string szUserID, + [MarshalAs(UnmanagedType.LPWStr)] + string szPassword); + + void Logoff(); + }; +} diff --git a/Technosoftware/OpcRcw/Technosoftware.OpcRcw.csproj b/Technosoftware/OpcRcw/Technosoftware.OpcRcw.csproj new file mode 100644 index 0000000..d00a82e --- /dev/null +++ b/Technosoftware/OpcRcw/Technosoftware.OpcRcw.csproj @@ -0,0 +1,17 @@ + + + + Technosoftware.OpcRcw + net6.0 + 9.0 + Technosoftware.DaAeHdaSolution.OpcRcw + OPC DA/AE/HDA Client Solution .NET + AnyCPU + + + + + + + + diff --git a/h-opc/h-opc/h-opc.csproj b/h-opc/h-opc/h-opc.csproj index 15f0fda..11ece50 100644 --- a/h-opc/h-opc/h-opc.csproj +++ b/h-opc/h-opc/h-opc.csproj @@ -9,7 +9,12 @@ - + + + + + +