From b8434dc0d00e35610ccc0d366ba1111596b51cd6 Mon Sep 17 00:00:00 2001 From: Andrew Cullen Date: Fri, 21 Jul 2017 11:47:09 -0400 Subject: [PATCH] Added EventFieldAttribute and updated BaseEvent and subclasses. Added Acknowlege and Refresh methods to SubscriptionBase. --- .../Ua/AcknowledgeableCondition.cs | 13 ++-- UaClient/ServiceModel/Ua/AlarmCondition.cs | 8 ++- UaClient/ServiceModel/Ua/BaseEvent.cs | 8 +++ UaClient/ServiceModel/Ua/Condition.cs | 9 ++- .../ServiceModel/Ua/EventFieldAttribute.cs | 49 +++++++++++++ UaClient/ServiceModel/Ua/EventHelper.cs | 72 +++++++++++++++++-- UaClient/ServiceModel/Ua/QualifiedName.cs | 2 +- UaClient/ServiceModel/Ua/SubscriptionBase.cs | 66 +++++++++++++++++ UaClient/ServiceModel/Ua/VariantExtensions.cs | 4 +- UaClient/Workstation.UaClient.csproj | 5 +- 10 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 UaClient/ServiceModel/Ua/EventFieldAttribute.cs diff --git a/UaClient/ServiceModel/Ua/AcknowledgeableCondition.cs b/UaClient/ServiceModel/Ua/AcknowledgeableCondition.cs index d5535b8..035c59b 100644 --- a/UaClient/ServiceModel/Ua/AcknowledgeableCondition.cs +++ b/UaClient/ServiceModel/Ua/AcknowledgeableCondition.cs @@ -1,6 +1,7 @@ // Copyright (c) Converter Systems LLC. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; namespace Workstation.ServiceModel.Ua @@ -10,17 +11,21 @@ namespace Workstation.ServiceModel.Ua /// public class AcknowledgeableCondition : Condition { - public bool AckedState { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.AcknowledgeableConditionType, browsePath: "AckedState/Id")] + public bool? AckedState { get; set; } - public bool ConfirmedState { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.AcknowledgeableConditionType, browsePath: "ConfirmedState/Id")] + public bool? ConfirmedState { get; set; } + [Obsolete] public override void Deserialize(Variant[] fields) { base.Deserialize(fields); - this.AckedState = fields[10].GetValueOrDefault(); - this.ConfirmedState = fields[11].GetValueOrDefault(); + this.AckedState = fields[10].GetValue() as bool?; + this.ConfirmedState = fields[11].GetValue() as bool?; } + [Obsolete] public override IEnumerable GetSelectClauses() { foreach (var clause in base.GetSelectClauses()) diff --git a/UaClient/ServiceModel/Ua/AlarmCondition.cs b/UaClient/ServiceModel/Ua/AlarmCondition.cs index 1eaf256..e58dc62 100644 --- a/UaClient/ServiceModel/Ua/AlarmCondition.cs +++ b/UaClient/ServiceModel/Ua/AlarmCondition.cs @@ -1,6 +1,7 @@ // Copyright (c) Converter Systems LLC. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; namespace Workstation.ServiceModel.Ua @@ -10,14 +11,17 @@ namespace Workstation.ServiceModel.Ua /// public class AlarmCondition : AcknowledgeableCondition { - public bool ActiveState { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.AlarmConditionType, browsePath: "ActiveState/Id")] + public bool? ActiveState { get; set; } + [Obsolete] public override void Deserialize(Variant[] fields) { base.Deserialize(fields); - this.ActiveState = fields[12].GetValueOrDefault(); + this.ActiveState = fields[12].GetValue() as bool?; } + [Obsolete] public override IEnumerable GetSelectClauses() { foreach (var clause in base.GetSelectClauses()) diff --git a/UaClient/ServiceModel/Ua/BaseEvent.cs b/UaClient/ServiceModel/Ua/BaseEvent.cs index 9083752..d0621d1 100644 --- a/UaClient/ServiceModel/Ua/BaseEvent.cs +++ b/UaClient/ServiceModel/Ua/BaseEvent.cs @@ -11,18 +11,25 @@ namespace Workstation.ServiceModel.Ua /// public class BaseEvent { + [EventField(typeDefinitionId: ObjectTypeIds.BaseEventType, browsePath: "EventId")] public byte[] EventId { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.BaseEventType, browsePath: "EventType")] public NodeId EventType { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.BaseEventType, browsePath: "SourceName")] public string SourceName { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.BaseEventType, browsePath: "Time")] public DateTime Time { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.BaseEventType, browsePath: "Message")] public LocalizedText Message { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.BaseEventType, browsePath: "Severity")] public ushort Severity { get; set; } + [Obsolete] public virtual void Deserialize(Variant[] fields) { this.EventId = fields[0].GetValueOrDefault(); @@ -33,6 +40,7 @@ public virtual void Deserialize(Variant[] fields) this.Severity = fields[5].GetValueOrDefault(); } + [Obsolete] public virtual IEnumerable GetSelectClauses() { yield return new SimpleAttributeOperand { TypeDefinitionId = NodeId.Parse(ObjectTypeIds.BaseEventType), BrowsePath = new[] { new QualifiedName("EventId") }, AttributeId = AttributeIds.Value }; diff --git a/UaClient/ServiceModel/Ua/Condition.cs b/UaClient/ServiceModel/Ua/Condition.cs index b0829dc..2155c8d 100644 --- a/UaClient/ServiceModel/Ua/Condition.cs +++ b/UaClient/ServiceModel/Ua/Condition.cs @@ -1,6 +1,7 @@ // Copyright (c) Converter Systems LLC. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; namespace Workstation.ServiceModel.Ua @@ -10,23 +11,29 @@ namespace Workstation.ServiceModel.Ua /// public class Condition : BaseEvent { + [EventField(typeDefinitionId: ObjectTypeIds.ConditionType, attributeId: AttributeIds.NodeId)] public NodeId ConditionId { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.ConditionType, browsePath: "ConditionName")] public string ConditionName { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.ConditionType, browsePath: "BranchId")] public NodeId BranchId { get; set; } + [EventField(typeDefinitionId: ObjectTypeIds.ConditionType, browsePath: "Retain")] public bool? Retain { get; set; } + [Obsolete] public override void Deserialize(Variant[] fields) { base.Deserialize(fields); this.ConditionId = fields[6].GetValueOrDefault(); this.ConditionName = fields[7].GetValueOrDefault(); this.BranchId = fields[8].GetValueOrDefault(); - this.Retain = fields[9].GetValueOrDefault(); + this.Retain = fields[9].GetValue() as bool?; } + [Obsolete] public override IEnumerable GetSelectClauses() { foreach (var clause in base.GetSelectClauses()) diff --git a/UaClient/ServiceModel/Ua/EventFieldAttribute.cs b/UaClient/ServiceModel/Ua/EventFieldAttribute.cs new file mode 100644 index 0000000..ad80910 --- /dev/null +++ b/UaClient/ServiceModel/Ua/EventFieldAttribute.cs @@ -0,0 +1,49 @@ +// Copyright (c) Converter Systems LLC. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Workstation.ServiceModel.Ua +{ + /// + /// Specifies the EventField that will be created for this property. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class EventFieldAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// the TypeDefinitionId. + /// the browse names, separated by '/'. + /// the attribute. + /// the range of array indexes. + public EventFieldAttribute(string typeDefinitionId = null, string browsePath = null, uint attributeId = AttributeIds.Value, string indexRange = null) + { + this.TypeDefinitionId = typeDefinitionId; + this.BrowsePath = browsePath; + this.AttributeId = attributeId; + this.IndexRange = indexRange; + } + + /// + /// Gets the TypeDefinitionId. + /// + public string TypeDefinitionId { get; } + + /// + /// Gets the BrowsePath. + /// + public string BrowsePath { get; } + + /// + /// Gets the attribute. + /// + public uint AttributeId { get; } + + /// + /// Gets the range of array indexes. + /// + public string IndexRange { get; } + } +} \ No newline at end of file diff --git a/UaClient/ServiceModel/Ua/EventHelper.cs b/UaClient/ServiceModel/Ua/EventHelper.cs index 1b56cda..05b2b60 100644 --- a/UaClient/ServiceModel/Ua/EventHelper.cs +++ b/UaClient/ServiceModel/Ua/EventHelper.cs @@ -2,38 +2,96 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace Workstation.ServiceModel.Ua { public static class EventHelper { + private static readonly Dictionary SelectClauseCache = new Dictionary(); + private static readonly Dictionary DeserializerCache = new Dictionary(); + public static T Deserialize(Variant[] eventFields) where T : BaseEvent, new() { - var e = new T(); - e.Deserialize(eventFields); + var e = Activator.CreateInstance(); + if (DeserializerCache.TryGetValue(typeof(T), out PropertyInfo[] infos)) + { + for (int i = 0; i < eventFields.Length; i++) + { + infos[i].SetValue(e, eventFields[i].GetValue()); + } + } + return e; } public static BaseEvent Deserialize(Type type, Variant[] eventFields) { var e = (BaseEvent)Activator.CreateInstance(type); - e.Deserialize(eventFields); + if (DeserializerCache.TryGetValue(type, out PropertyInfo[] infos)) + { + for (int i = 0; i < eventFields.Length; i++) + { + infos[i].SetValue(e, eventFields[i].GetValue()); + } + } + return e; } public static SimpleAttributeOperand[] GetSelectClauses() where T : BaseEvent, new() { - var e = new T(); - return e.GetSelectClauses().ToArray(); + var type = typeof(T); + if (SelectClauseCache.TryGetValue(type, out SimpleAttributeOperand[] clauses)) + { + return clauses; + } + + RegisterSelectClauseAndDeserializer(type); + return SelectClauseCache[type]; } public static SimpleAttributeOperand[] GetSelectClauses(Type type) { - var e = (BaseEvent)Activator.CreateInstance(type); - return e.GetSelectClauses().ToArray(); + if (SelectClauseCache.TryGetValue(type, out SimpleAttributeOperand[] clauses)) + { + return clauses; + } + + RegisterSelectClauseAndDeserializer(type); + return SelectClauseCache[type]; + } + + private static void RegisterSelectClauseAndDeserializer(Type type) + { + var clauseList = new List(); + var infoList = new List(); + foreach (var info in type.GetRuntimeProperties()) + { + var efa = info.GetCustomAttribute(); + if (efa == null || string.IsNullOrEmpty(efa.TypeDefinitionId)) + { + continue; + } + + var clause = new SimpleAttributeOperand + { + TypeDefinitionId = NodeId.Parse(efa.TypeDefinitionId), + BrowsePath = !String.IsNullOrWhiteSpace(efa.BrowsePath) ? efa.BrowsePath.Split('/').Select(s => QualifiedName.Parse(s)).ToArray() : new QualifiedName[0], + AttributeId = efa.AttributeId, + IndexRange = efa.IndexRange + }; + + clauseList.Add(clause); + infoList.Add(info); + } + + SelectClauseCache[type] = clauseList.ToArray(); + DeserializerCache[type] = infoList.ToArray(); } } } diff --git a/UaClient/ServiceModel/Ua/QualifiedName.cs b/UaClient/ServiceModel/Ua/QualifiedName.cs index 47e3b50..798da9f 100644 --- a/UaClient/ServiceModel/Ua/QualifiedName.cs +++ b/UaClient/ServiceModel/Ua/QualifiedName.cs @@ -56,7 +56,7 @@ public static bool TryParse(string s, out QualifiedName qname) } qname = new QualifiedName(name, ns); - return false; + return true; } catch (Exception) { diff --git a/UaClient/ServiceModel/Ua/SubscriptionBase.cs b/UaClient/ServiceModel/Ua/SubscriptionBase.cs index 0a68bbd..0ed33b7 100644 --- a/UaClient/ServiceModel/Ua/SubscriptionBase.cs +++ b/UaClient/ServiceModel/Ua/SubscriptionBase.cs @@ -215,6 +215,72 @@ public CommunicationState State private set { this.SetProperty(ref this.state, value); } } + /// + /// Gets the current subscription Id. + /// + public uint SubscriptionId => this.state == CommunicationState.Opened ? this.subscriptionId : 0u; + + /// + /// Requests a Refresh of all Conditions. + /// + /// A representing the asynchronous operation. + public async Task ConditionRefresh() + { + if (this.State != CommunicationState.Opened) + { + return StatusCodes.BadServerNotConnected; + } + + var response = await this.InnerChannel.CallAsync(new CallRequest + { + MethodsToCall = new[] + { + new CallMethodRequest + { + ObjectId = NodeId.Parse(ObjectTypeIds.ConditionType), + MethodId = NodeId.Parse(MethodIds.ConditionType_ConditionRefresh), + InputArguments = new Variant[] { this.SubscriptionId } + } + } + }); + + return response.Results[0].StatusCode; + } + + /// + /// Acknowledges a condition. + /// + /// an AcknowledgeableCondition. + /// a comment. + /// A representing the asynchronous operation. + public async Task Acknowledge(AcknowledgeableCondition condition, LocalizedText comment = null) + { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + + if (this.State != CommunicationState.Opened) + { + return StatusCodes.BadServerNotConnected; + } + + var response = await this.InnerChannel.CallAsync(new CallRequest + { + MethodsToCall = new[] + { + new CallMethodRequest + { + ObjectId = condition.ConditionId, + MethodId = NodeId.Parse(MethodIds.AcknowledgeableConditionType_Acknowledge), + InputArguments = new Variant[] { condition.EventId, comment } // ?? new LocalizedText(string.Empty) } + } + } + }); + + return response.Results[0].StatusCode; + } + /// /// Gets the inner channel. /// diff --git a/UaClient/ServiceModel/Ua/VariantExtensions.cs b/UaClient/ServiceModel/Ua/VariantExtensions.cs index 88bbd2f..71713e7 100644 --- a/UaClient/ServiceModel/Ua/VariantExtensions.cs +++ b/UaClient/ServiceModel/Ua/VariantExtensions.cs @@ -9,10 +9,10 @@ namespace Workstation.ServiceModel.Ua public static class VariantExtensions { /// - /// Gets the value of the Variant, or the default value for the type. + /// Gets the value of the Variant, or null. /// /// The Variant. - /// The value if an instance of the specified Type, otherwise the Type's default value. + /// The value if an instance of the specified Type, otherwise null. public static object GetValue(this Variant variant) { var value = variant.Value; diff --git a/UaClient/Workstation.UaClient.csproj b/UaClient/Workstation.UaClient.csproj index a7a9c20..b75d643 100644 --- a/UaClient/Workstation.UaClient.csproj +++ b/UaClient/Workstation.UaClient.csproj @@ -4,7 +4,7 @@ netstandard1.4;net45 Workstation.UaClient Workstation.UaClient - 2.0.1 + 2.0.2 Andrew Cullen Converter Systems LLC https://github.com/convertersystems/opc-ua-client @@ -13,8 +13,9 @@ opc, opc-ua, iiot https://github.com/convertersystems/opc-ua-client Copyright © 2017 Converter Systems LLC. - 2.0.1.0 + 2.0.2.0 False + 2.0.2.0