Skip to content

Commit

Permalink
Added EventFieldAttribute and updated BaseEvent and subclasses. Added…
Browse files Browse the repository at this point in the history
… Acknowlege and Refresh methods to SubscriptionBase.
  • Loading branch information
awcullen committed Jul 21, 2017
1 parent c962e7e commit b8434dc
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 19 deletions.
13 changes: 9 additions & 4 deletions UaClient/ServiceModel/Ua/AcknowledgeableCondition.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,17 +11,21 @@ namespace Workstation.ServiceModel.Ua
/// </summary>
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<bool>();
this.ConfirmedState = fields[11].GetValueOrDefault<bool>();
this.AckedState = fields[10].GetValue() as bool?;
this.ConfirmedState = fields[11].GetValue() as bool?;
}

[Obsolete]
public override IEnumerable<SimpleAttributeOperand> GetSelectClauses()
{
foreach (var clause in base.GetSelectClauses())
Expand Down
8 changes: 6 additions & 2 deletions UaClient/ServiceModel/Ua/AlarmCondition.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,14 +11,17 @@ namespace Workstation.ServiceModel.Ua
/// </summary>
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<bool>();
this.ActiveState = fields[12].GetValue() as bool?;
}

[Obsolete]
public override IEnumerable<SimpleAttributeOperand> GetSelectClauses()
{
foreach (var clause in base.GetSelectClauses())
Expand Down
8 changes: 8 additions & 0 deletions UaClient/ServiceModel/Ua/BaseEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@ namespace Workstation.ServiceModel.Ua
/// </summary>
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<byte[]>();
Expand All @@ -33,6 +40,7 @@ public virtual void Deserialize(Variant[] fields)
this.Severity = fields[5].GetValueOrDefault<ushort>();
}

[Obsolete]
public virtual IEnumerable<SimpleAttributeOperand> GetSelectClauses()
{
yield return new SimpleAttributeOperand { TypeDefinitionId = NodeId.Parse(ObjectTypeIds.BaseEventType), BrowsePath = new[] { new QualifiedName("EventId") }, AttributeId = AttributeIds.Value };
Expand Down
9 changes: 8 additions & 1 deletion UaClient/ServiceModel/Ua/Condition.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,23 +11,29 @@ namespace Workstation.ServiceModel.Ua
/// </summary>
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<NodeId>();
this.ConditionName = fields[7].GetValueOrDefault<string>();
this.BranchId = fields[8].GetValueOrDefault<NodeId>();
this.Retain = fields[9].GetValueOrDefault<bool>();
this.Retain = fields[9].GetValue() as bool?;
}

[Obsolete]
public override IEnumerable<SimpleAttributeOperand> GetSelectClauses()
{
foreach (var clause in base.GetSelectClauses())
Expand Down
49 changes: 49 additions & 0 deletions UaClient/ServiceModel/Ua/EventFieldAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Specifies the EventField that will be created for this property.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class EventFieldAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="EventFieldAttribute"/> class.
/// </summary>
/// <param name="typeDefinitionId">the TypeDefinitionId.</param>
/// <param name="browsePath">the browse names, separated by '/'.</param>
/// <param name="attributeId">the attribute.</param>
/// <param name="indexRange">the range of array indexes.</param>
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;
}

/// <summary>
/// Gets the TypeDefinitionId.
/// </summary>
public string TypeDefinitionId { get; }

/// <summary>
/// Gets the BrowsePath.
/// </summary>
public string BrowsePath { get; }

/// <summary>
/// Gets the attribute.
/// </summary>
public uint AttributeId { get; }

/// <summary>
/// Gets the range of array indexes.
/// </summary>
public string IndexRange { get; }
}
}
72 changes: 65 additions & 7 deletions UaClient/ServiceModel/Ua/EventHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type, SimpleAttributeOperand[]> SelectClauseCache = new Dictionary<Type, SimpleAttributeOperand[]>();
private static readonly Dictionary<Type, PropertyInfo[]> DeserializerCache = new Dictionary<Type, PropertyInfo[]>();

public static T Deserialize<T>(Variant[] eventFields)
where T : BaseEvent, new()
{
var e = new T();
e.Deserialize(eventFields);
var e = Activator.CreateInstance<T>();
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<T>()
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<SimpleAttributeOperand>();
var infoList = new List<PropertyInfo>();
foreach (var info in type.GetRuntimeProperties())
{
var efa = info.GetCustomAttribute<EventFieldAttribute>();
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();
}
}
}
2 changes: 1 addition & 1 deletion UaClient/ServiceModel/Ua/QualifiedName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static bool TryParse(string s, out QualifiedName qname)
}

qname = new QualifiedName(name, ns);
return false;
return true;
}
catch (Exception)
{
Expand Down
66 changes: 66 additions & 0 deletions UaClient/ServiceModel/Ua/SubscriptionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,72 @@ public CommunicationState State
private set { this.SetProperty(ref this.state, value); }
}

/// <summary>
/// Gets the current subscription Id.
/// </summary>
public uint SubscriptionId => this.state == CommunicationState.Opened ? this.subscriptionId : 0u;

/// <summary>
/// Requests a Refresh of all Conditions.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task<StatusCode> 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;
}

/// <summary>
/// Acknowledges a condition.
/// </summary>
/// <param name="condition">an AcknowledgeableCondition.</param>
/// <param name="comment">a comment.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task<StatusCode> 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;
}

/// <summary>
/// Gets the inner channel.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions UaClient/ServiceModel/Ua/VariantExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ namespace Workstation.ServiceModel.Ua
public static class VariantExtensions
{
/// <summary>
/// Gets the value of the Variant, or the default value for the type.
/// Gets the value of the Variant, or null.
/// </summary>
/// <param name="variant">The Variant.</param>
/// <returns>The value if an instance of the specified Type, otherwise the Type's default value.</returns>
/// <returns>The value if an instance of the specified Type, otherwise null.</returns>
public static object GetValue(this Variant variant)
{
var value = variant.Value;
Expand Down
Loading

0 comments on commit b8434dc

Please sign in to comment.