OPCFoundation / UA-.NETStandard

OPC Unified Architecture .NET Standard
Other
1.94k stars 942 forks source link

Decoding structure event property value #944

Open 11v1 opened 4 years ago

11v1 commented 4 years ago

Hi.

I have custom structure and event:

  <opc:DataType SymbolicName="AcknoledgeEventUniqueIDType" BaseType="ua:Structure">
    <opc:Fields>
      <opc:Field Name="EventID" DataType="ua:ByteString"></opc:Field>
      <opc:Field Name="Time" DataType="ua:DateTime"></opc:Field>
      <opc:Field Name="SourceName" DataType="ua:String"></opc:Field>
    </opc:Fields>
  </opc:DataType>

  <opc:ObjectType SymbolicName="AcknoledgeBaseEventType" BaseType="ua:BaseEventType"/>

  <opc:ObjectType SymbolicName="AcknoledgeNotificationEventType" BaseType="AcknoledgeBaseEventType">
    <opc:Children>
      <opc:Property SymbolicName="EventUniqueID" DataType="AcknoledgeEventUniqueIDType" ModellingRule="Mandatory">
      </opc:Property>
    </opc:Children>
  </opc:ObjectType>

Server works fine. It generates new event of type AcknoledgeNotificationEventType, client receives this event, it even receives my custom field EventUniqueID but it can not decode it and I get event's EventUniqueID.Value == null. This is my MonitoredItem Notification handler:

        private void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
        {
            try
            {
                if (e == null)
                    return;

                if (!(e.NotificationValue is EventFieldList notification))
                    return;

                // check the type of event.
                NodeId eventTypeId = ClientUtils.FindEventType(monitoredItem, notification);

                // ignore unknown events.
                if (NodeId.IsNull(eventTypeId))
                    return;

                Connection connection = GetMonitoredItemConnection(monitoredItem);
                // construct the audit object.
                BaseEventState @event;
                lock (_knownEventTypes)
                    @event = ClientUtils.ConstructEvent(
                        connection.Session,
                        monitoredItem,
                        notification,
                        _knownEventTypes[connection],
                        _eventTypeMappings[connection]);

                if (@event == null)
                    return;

                switch (@event)
                {
                    case AcknoledgeEventState ev:
                        OnMessageReceived(new MessageEventArgs(ev.ToMessage()));
                        break;
                    case AcknoledgeNotificationEventState no:
                        OnMessageAcknoledged(new AcknoledgedEventArgs(AcknoledgeEventState.ConvertEventId(no.EventUniqueID.Value.EventID),
                            no.EventUniqueID.Value.Time, no.EventUniqueID.Value.SourceName, no.Time.Value));
                        break;
                    default:                        
                        return;
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex, Properties.Resources.LogSourceMonitoredItemNotificationError, new object[] { FullName, monitoredItem, e });
            }
        }

As I reviewed my e.NotificationValue.EventFields[10] is of type ExtensionObject. When it's value in tried to be resolved in BaseVariableState.DecodeExtensionObject it gets null. In BaseVariableState.DecodeExtensionObject only 2 values of Encoding property are handeled: Binary and Xml. My field comes with Encoding = EncodeableObject. This is the code how event is generated in server:

        public void MessageAcknoledged(object sender, AcknoledgedEventArgs e)
        {
            // construct the event.
            using (AcknoledgeNotificationEventState @event = new AcknoledgeNotificationEventState(null))
            {
                @event.Initialize(SystemContext, null, 0, null);
                AcknoledgeEventUniqueIDType uniqueId = new AcknoledgeEventUniqueIDType()
                {
                    EventID = AcknoledgeEventState.ConvertEventId(e.Id),
                    Time = e.Time,
                    SourceName = e.Provider
                };
                @event.SetChildValue(SystemContext, Alarms.OpcUa.BrowseNames.EventUniqueID, uniqueId, false);
                @event.SetChildValue(SystemContext, Opc.Ua.BrowseNames.Time, e.AcknoledgeTime, false);

                _root.ReportEvent(SystemContext, @event);
            }
        }

In Unified Automation UaExpert I can see my event's property EventUniqueID properly. What am I doing wrong?

11v1 commented 4 years ago

Further investigation showed following: method BaseVariableState.DecodeExtensionObject should return value by this condition:

if (targetType.IsInstanceOfType(extension.Body))
{
    return extension.Body;
}

But here is the strange thing: targetType.FullName = VG.Alarms.OpcUa.AcknoledgeEventUniqueIDType which is my type generated by ModelCompiler:

namespace VG.Alarms.OpcUa
{
    #region AcknoledgeEventUniqueIDType Class
    #if (!OPCUA_EXCLUDE_AcknoledgeEventUniqueIDType)
    /// <summary>
    /// A description for the AcknoledgeEventUniqueIDType DataType.
    /// </summary>
    /// <exclude />
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")]
    [DataContract(Namespace = VG.Alarms.OpcUa.Namespaces.Alarms)]
    public partial class AcknoledgeEventUniqueIDType : IEncodeable
...

But extension.Body.GetType().FullName = Opc.Ua.ComplexTypes.VG.Alarms.OpcUa.AcknoledgeEventUniqueIDType which is ... I don't know what, some mix of namespaces I guess.

11v1 commented 4 years ago

Quickstart SimpleEvents Client from "UA Quickstart Applications" have the same issue with one exception: first 4 events can decode structure properly, afterwards it can't. Obviously it linked somehow to loading complex types from server. Everything works fine (structure event properties) if I remove following lines in client after session connection established:

var typeSystemLoader = new ComplexTypeSystem(m_session);
await typeSystemLoader.Load();