Beispiel #1
0
 /// <summary>
 /// Saves the element in cache.
 /// </summary>
 /// <param name="element">The element.</param>
 private void SaveElementInCache(DaElement element)
 {
     lock (m_cache)
     {
         m_cache[element.ItemId] = element;
     }
 }
Beispiel #2
0
        /// <summary>
        /// Finds the element and updates the name if it is not already cached.
        /// </summary>
        /// <param name="itemId">The item id.</param>
        /// <param name="name">The name.</param>
        /// <param name="parentId">The parent id.</param>
        /// <returns>The element.</returns>
        private DaElement FindElement(string itemId, string name, string parentId)
        {
            if (String.IsNullOrEmpty(itemId))
            {
                return(null);
            }

            // look in cache for existing element.
            DaElement element = null;

            lock (m_cache)
            {
                if (!m_cache.TryGetValue(itemId, out element))
                {
                    element = null;
                }
            }

            // create a new element.
            if (element == null)
            {
                element = CreateElement(itemId, name, parentId);
                SaveElementInCache(element);
            }

            // update the element.
            element.Name     = name;
            element.ParentId = parentId;

            return(element);
        }
Beispiel #3
0
        /// <summary>
        /// Creates a new element.
        /// </summary>
        /// <param name="itemId">The item id.</param>
        /// <param name="name">The name.</param>
        /// <param name="parentId">The parent id.</param>
        /// <returns>The element.</returns>
        private DaElement CreateElement(string itemId, string name, string parentId)
        {
            DaElement element = new DaElement();

            element.ItemId = itemId;
            UpdateElement(element, name, parentId);
            return(element);
        }
Beispiel #4
0
        /// <summary>
        /// Constructs a branch or item node from a DaElement returned from the COM server.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="element">The element.</param>
        /// <param name="namespaceIndex">Index of the namespace for the NodeId.</param>
        /// <returns>The node.</returns>
        public static NodeState ConstructElement(ISystemContext context, DaElement element, ushort namespaceIndex)
        {
            if (element.ElementType == DaElementType.Branch)
            {
                return(new DaBranchState(context, element, namespaceIndex));
            }

            return(new DaItemState(context, element, namespaceIndex));
        }
Beispiel #5
0
        /// <summary>
        /// Finds the property metadata for the specified item id.
        /// </summary>
        /// <param name="itemId">The item id.</param>
        /// <param name="propertyId">The property id.</param>
        /// <returns>The metadata for the property.</returns>
        public DaProperty FindProperty(string itemId, int propertyId)
        {
            if (String.IsNullOrEmpty(itemId))
            {
                return(null);
            }

            // check the cache.
            DaElement element = null;

            lock (m_cache)
            {
                if (m_cache.TryGetValue(itemId, out element))
                {
                    if (element.Properties != null)
                    {
                        for (int ii = 0; ii < element.Properties.Length; ii++)
                        {
                            if (element.Properties[ii].PropertyId == propertyId)
                            {
                                return(element.Properties[ii]);
                            }
                        }
                    }
                }
            }

            // check if the element has to be loaded.
            if (element == null)
            {
                element = FindElement(itemId);

                if (element == null)
                {
                    return(null);
                }
            }

            // update the property list.
            element.Properties = ReadAvailableProperties(itemId, false);

            if (element.Properties != null)
            {
                for (int ii = 0; ii < element.Properties.Length; ii++)
                {
                    if (element.Properties[ii].PropertyId == propertyId)
                    {
                        return(element.Properties[ii]);
                    }
                }
            }

            // not found.
            return(null);
        }
Beispiel #6
0
        /// <summary>
        /// Initializes the node from the element.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="element">The element.</param>
        /// <param name="namespaceIndex">Index of the namespace.</param>
        public void Initialize(ISystemContext context, DaElement element, ushort namespaceIndex)
        {
            m_element = element;

            if (element == null)
            {
                return;
            }

            this.NodeId      = ModelUtils.ConstructIdForDaElement(element.ItemId, -1, namespaceIndex);
            this.BrowseName  = new QualifiedName(element.Name, namespaceIndex);
            this.DisplayName = new LocalizedText(element.Name);
        }
Beispiel #7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="DaItemState"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="element">The element.</param>
        /// <param name="namespaceIndex">Index of the namespace.</param>
        public DaItemState(
            ISystemContext context,
            DaElement element,
            ushort namespaceIndex)
            :
            base(null)
        {
            this.TypeDefinitionId = Opc.Ua.VariableTypeIds.DataItemType;
            this.Description      = null;
            this.WriteMask        = 0;
            this.UserWriteMask    = 0;

            if (element != null)
            {
                Initialize(context, element, namespaceIndex);
            }
        }
Beispiel #8
0
        /// <summary>
        /// Finds the item id for the parent of the element.
        /// </summary>
        /// <param name="itemId">The item id.</param>
        /// <returns>The item id for the parent of the element.</returns>
        public string FindElementParentId(string itemId)
        {
            if (String.IsNullOrEmpty(itemId))
            {
                return(null);
            }

            // check in cache.
            DaElement element = null;

            lock (m_cache)
            {
                if (m_cache.TryGetValue(itemId, out element))
                {
                    if (element.ParentId != null)
                    {
                        return(element.ParentId);
                    }
                }
            }

            // try extracting the name by parsing the item id.
            string name     = null;
            string parentId = null;

            if (ParseItemId(itemId, out name, out parentId))
            {
                element = CreateElement(itemId, name, parentId);
            }

            // need to do it the hard way by searching the address space.
            else
            {
                IDaElementBrowser browser = CreateBrowser(itemId);
                element = browser.Find(itemId, false);
                browser.Dispose();
            }

            // save element in the cache.
            if (element != null)
            {
                SaveElementInCache(element);
            }

            return(element.ParentId);
        }
Beispiel #9
0
        /// <summary>
        /// Initializes a new instance of the <see cref="DaBranchState"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="element">The element.</param>
        /// <param name="namespaceIndex">Index of the namespace.</param>
        public DaBranchState(
            ISystemContext context, 
            DaElement element, 
            ushort namespaceIndex)
        : 
            base(null)
        {
            this.TypeDefinitionId = Opc.Ua.ObjectTypeIds.FolderType;
            this.Description = null;
            this.WriteMask = 0;
            this.UserWriteMask = 0;
            this.EventNotifier = EventNotifiers.None;

            if (element != null)
            {
                Initialize(context, element, namespaceIndex);
            }
        }
Beispiel #10
0
        /// <summary>
        /// Initializes a new instance of the <see cref="DaBranchState"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="element">The element.</param>
        /// <param name="namespaceIndex">Index of the namespace.</param>
        public DaBranchState(
            ISystemContext context,
            DaElement element,
            ushort namespaceIndex)
            :
            base(null)
        {
            this.TypeDefinitionId = Opc.Ua.ObjectTypeIds.FolderType;
            this.Description      = null;
            this.WriteMask        = 0;
            this.UserWriteMask    = 0;
            this.EventNotifier    = EventNotifiers.None;

            if (element != null)
            {
                Initialize(context, element, namespaceIndex);
            }
        }
Beispiel #11
0
        /// <summary>
        /// Initializes the node from the element.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="element">The element.</param>
        /// <param name="namespaceIndex">Index of the namespace.</param>
        public void Initialize(ISystemContext context, DaElement element, ushort namespaceIndex)
        {
            m_element = element;

            if (element == null)
            {
                return;
            }

            this.NodeId = ModelUtils.ConstructIdForDaElement(element.ItemId, -1, namespaceIndex);
            this.BrowseName = new QualifiedName(element.Name, namespaceIndex);
            this.DisplayName = new LocalizedText(element.Name);
        }
Beispiel #12
0
            /// <summary>
            /// Finds the element with the specified item id.
            /// </summary>
            /// <param name="parentId">The parent id.</param>
            /// <param name="targetId">The target id.</param>
            /// <param name="isItem">if set to <c>true</c> the element is a item.</param>
            /// <param name="recursive">if set to <c>true</c> [recursive].</param>
            /// <returns>The element if found.</returns>
            public DaElement Find(string parentId, string targetId, bool isItem, bool recursive)
            {
                // create the enumerator if not already created.
                m_enumerator = m_clone.CreateEnumerator(false);

                // a null indicates an error.
                if (m_enumerator == null)
                {
                    return(null);
                }

                string name = null;

                // process all items.
                do
                {
                    // fetch the next name.
                    name = m_enumerator.Next();

                    // a null indicates the end of list.
                    if (name == null)
                    {
                        break;
                    }

                    // fetch the item id.
                    string itemId = m_clone.GetItemId(name);

                    // fetch the metadata.
                    DaElement element = m_client.FindElement(itemId, name, parentId);

                    // check if target found.
                    if (targetId == itemId)
                    {
                        return(element);
                    }
                }while (true);

                m_enumerator.Dispose();
                m_enumerator = null;

                List <DaElement> branches = new List <DaElement>();

                // fetch the branches of the target is a branch or if a recursive search is required.
                if (!isItem || recursive)
                {
                    // need to fetch list of branches to search.
                    m_enumerator = m_clone.CreateEnumerator(true);

                    if (m_enumerator == null)
                    {
                        return(null);
                    }

                    // process all branches.
                    do
                    {
                        name = m_enumerator.Next();

                        // a null indicates the end of list.
                        if (name == null)
                        {
                            break;
                        }

                        // fetch the item id.
                        string itemId = m_clone.GetItemId(name);

                        // fetch the metadata.
                        DaElement element = m_client.FindElement(itemId, name, parentId);

                        // save branch for recursive search if not found at this level.
                        if (recursive)
                        {
                            branches.Add(element);
                        }

                        // check if target found.
                        if (targetId == itemId)
                        {
                            return(element);
                        }
                    }while (name != null);

                    m_enumerator.Dispose();
                    m_enumerator = null;
                }

                // all done if not doing a recursive search.
                if (!recursive)
                {
                    return(null);
                }

                // recursively search hierarchy.
                for (int ii = 0; ii < branches.Count; ii++)
                {
                    m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_DOWN, branches[ii].Name);

                    DaElement element = Find(branches[ii].ItemId, targetId, isItem, recursive);

                    if (element != null)
                    {
                        return(element);
                    }

                    m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_UP, String.Empty);
                }

                // not found.
                return(null);
            }
Beispiel #13
0
        /// <summary>
        /// Returns the next child.
        /// </summary>
        private NodeStateReference NextChild(Stage stage)
        {
            ComDaClientManager system = (ComDaClientManager)this.SystemContext.SystemHandle;
            ComDaClient        client = system.SelectClient((ServerSystemContext)SystemContext);

            DaElement element = null;

            if (stage == Stage.Children)
            {
                if (m_browser == null)
                {
                    return(null);
                }

                element = m_browser.Next();

                if (element == null)
                {
                    return(null);
                }

                // construct the node.
                NodeState node = ModelUtils.ConstructElement(SystemContext, element, m_namespaceIndex);

                // return the reference.
                return(new NodeStateReference(ReferenceTypeIds.Organizes, false, node));
            }

            if (stage == Stage.Properties)
            {
                if (m_properties == null)
                {
                    return(null);
                }

                for (int ii = m_position; ii < m_properties.Length; ii++)
                {
                    if (m_properties[ii].PropertyId <= PropertyIds.TimeZone)
                    {
                        continue;
                    }

                    m_position = ii + 1;

                    // construct the node.
                    NodeState node = ModelUtils.ConstructProperty(SystemContext, m_itemId, m_properties[ii], m_namespaceIndex);

                    // return the reference.
                    return(new NodeStateReference(ReferenceTypeIds.HasProperty, false, node));
                }

                // all done.
                return(null);
            }

            if (stage == Stage.Parents)
            {
                if (m_parentId != null)
                {
                    NodeId parentId = ModelUtils.ConstructIdForDaElement(m_parentId, -1, m_namespaceIndex);
                    m_parentId = null;
                    return(new NodeStateReference(ReferenceTypeIds.Organizes, true, parentId));
                }
            }

            return(null);
        }
Beispiel #14
0
        /// <summary>
        /// Initializes the node from the element.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="element">The element.</param>
        /// <param name="namespaceIndex">Index of the namespace.</param>
        public void Initialize(ISystemContext context, DaElement element, ushort namespaceIndex)
        {
            m_element = element;

            if (element == null)
            {
                return;
            }

            this.NodeId      = ModelUtils.ConstructIdForDaElement(element.ItemId, -1, namespaceIndex);
            this.BrowseName  = new QualifiedName(element.Name, namespaceIndex);
            this.DisplayName = new LocalizedText(element.Name);

            // check if TimeZone is supported.
            if (element.TimeZone != null)
            {
                PropertyState property = this.AddProperty <Range>(Opc.Ua.BrowseNames.TimeZone, DataTypeIds.TimeZoneDataType, ValueRanks.Scalar);
                property.NodeId = ModelUtils.ConstructIdForComponent(property, namespaceIndex);
                property.Value  = new Range(element.HighIR, element.LowIR);
            }

            // set the TypeDefinition based on the ElementType.
            switch (element.ElementType)
            {
            case DaElementType.AnalogItem:
            {
                this.TypeDefinitionId = Opc.Ua.VariableTypeIds.AnalogItemType;

                // EURange is always present.
                PropertyState property = this.AddProperty <Range>(Opc.Ua.BrowseNames.EURange, DataTypeIds.Range, ValueRanks.Scalar);
                property.NodeId = ModelUtils.ConstructIdForComponent(property, namespaceIndex);
                property.Value  = new Range(element.HighEU, element.LowEU);

                // check if InstrumentRange is supported.
                if (element.HighIR != 0 || element.LowIR != 0)
                {
                    property        = this.AddProperty <Range>(Opc.Ua.BrowseNames.InstrumentRange, DataTypeIds.Range, ValueRanks.Scalar);
                    property.NodeId = ModelUtils.ConstructIdForComponent(property, namespaceIndex);
                    property.Value  = new Range(element.HighIR, element.LowIR);
                }

                // check if EngineeringUnits is supported.
                if (element.EngineeringUnits != null)
                {
                    property        = this.AddProperty <EUInformation>(Opc.Ua.BrowseNames.EngineeringUnits, DataTypeIds.EUInformation, ValueRanks.Scalar);
                    property.NodeId = ModelUtils.ConstructIdForComponent(property, namespaceIndex);

                    // use the server's namespace uri to qualify the engineering units.
                    string namespaceUri = context.NamespaceUris.GetString(namespaceIndex);
                    property.Value = new EUInformation(element.EngineeringUnits, namespaceUri);
                }

                break;
            }

            case DaElementType.DigitalItem:
            {
                this.TypeDefinitionId = Opc.Ua.VariableTypeIds.TwoStateDiscreteType;
                this.EnumStrings      = new PropertyState <LocalizedText[]>(this);

                // check if CloseLabel is supported.
                if (element.CloseLabel != null)
                {
                    PropertyState property = this.AddProperty <LocalizedText>(Opc.Ua.BrowseNames.TrueState, DataTypeIds.LocalizedText, ValueRanks.Scalar);
                    property.NodeId = ModelUtils.ConstructIdForComponent(property, namespaceIndex);
                    property.Value  = element.CloseLabel;
                }

                // check if OpenLabel is supported.
                if (element.OpenLabel != null)
                {
                    PropertyState property = this.AddProperty <LocalizedText>(Opc.Ua.BrowseNames.FalseState, DataTypeIds.LocalizedText, ValueRanks.Scalar);
                    property.NodeId = ModelUtils.ConstructIdForComponent(property, namespaceIndex);
                    property.Value  = element.OpenLabel;
                }

                break;
            }

            case DaElementType.EnumeratedItem:
            {
                this.TypeDefinitionId = Opc.Ua.VariableTypeIds.MultiStateDiscreteType;

                // check if EuInfo is supported.
                if (element.EuInfo != null)
                {
                    PropertyState property = this.AddProperty <LocalizedText[]>(Opc.Ua.BrowseNames.EnumStrings, DataTypeIds.LocalizedText, ValueRanks.OneDimension);
                    property.NodeId = ModelUtils.ConstructIdForComponent(property, namespaceIndex);

                    LocalizedText[] strings = new LocalizedText[element.EuInfo.Length];

                    for (int ii = 0; ii < strings.Length; ii++)
                    {
                        strings[ii] = element.EuInfo[ii];
                    }

                    property.Value = strings;
                }

                break;
            }
            }

            if (element.Description != null)
            {
                this.Description = element.Description;
            }

            this.Value      = null;
            this.StatusCode = StatusCodes.BadWaitingForInitialData;
            this.Timestamp  = DateTime.UtcNow;

            bool isArray = false;

            this.DataType  = ComUtils.GetDataTypeId(element.DataType, out isArray);
            this.ValueRank = (isArray)?ValueRanks.OneOrMoreDimensions:ValueRanks.Scalar;

            this.AccessLevel = AccessLevels.None;

            if ((element.AccessRights & OpcRcw.Da.Constants.OPC_READABLE) != 0)
            {
                this.AccessLevel |= AccessLevels.CurrentRead;
            }

            if ((element.AccessRights & OpcRcw.Da.Constants.OPC_WRITEABLE) != 0)
            {
                this.AccessLevel |= AccessLevels.CurrentWrite;
            }

            this.UserAccessLevel         = this.AccessLevel;
            this.MinimumSamplingInterval = element.ScanRate;
        }
Beispiel #15
0
            /// <summary>
            /// Returns the next DA element.
            /// </summary>
            /// <returns>A DA element. Null if nothing left to browse.</returns>
            public DaElement Next()
            {
                // check if already completed.
                if (m_completed)
                {
                    return(null);
                }

                // create the enumerator if not already created.
                if (m_enumerator == null)
                {
                    // need to clone the client since ChangeBrowsePosition prevents multiple
                    // simultaneous browse operations.
                    m_clone = m_client.CloneClient();
                    m_clone.CreateInstance();

                    // nothing to browse if change browse position failed.
                    if (!m_clone.ChangeBrowsePosition(OPCBROWSEDIRECTION.OPC_BROWSE_TO, m_itemId))
                    {
                        return(null);
                    }

                    m_enumerator = m_clone.CreateEnumerator(false);
                    m_branches   = false;

                    // a null indicates an error.
                    if (m_enumerator == null)
                    {
                        m_completed = true;
                        return(null);
                    }
                }

                // need a loop in case errors occur fetching element metadata.
                DaElement element = null;

                do
                {
                    // fetch the next name.
                    string name = m_enumerator.Next();

                    // a null indicates the end of list.
                    if (name == null)
                    {
                        if (!m_branches)
                        {
                            m_branches = true;
                            m_enumerator.Dispose();
                            m_enumerator = m_clone.CreateEnumerator(true);
                            continue;
                        }

                        m_completed = true;
                        return(null);
                    }

                    // suppress duplicates when a item is also a branch.
                    if (m_branches)
                    {
                        if (m_itemNames != null)
                        {
                            if (m_itemNames.ContainsKey(name))
                            {
                                continue;
                            }
                        }
                    }

                    // save the item name to allow checks for duplicates.
                    else
                    {
                        if (m_itemNames == null)
                        {
                            m_itemNames = new Dictionary <string, object>();
                        }

                        m_itemNames[name] = null;
                    }

                    // fetch the item id.
                    string itemId = m_clone.GetItemId(name);

                    // fetch the metadata.
                    element = m_client.FindElement(itemId, name, m_itemId);
                }while (element == null);

                // return element.
                return(element);
            }
Beispiel #16
0
        /// <summary>
        /// Updates a element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="name">The name.</param>
        /// <param name="parentId">The parent id.</param>
        private void UpdateElement(DaElement element, string name, string parentId)
        {
            // only update the name if specified.
            if (name != null)
            {
                element.Name = name;
            }

            // same for the parent id.
            if (parentId != null)
            {
                element.ParentId = parentId;
            }

            // read item property values.
            DaValue[] values = ReadPropertyValues(element.ItemId, m_CoreProperties);

            // must be an item if the data type property exists.
            if (values[0].Error >= 0)
            {
                element.ElementType      = DaElementType.Item;
                element.DataType         = values[0].GetValue <short>();
                element.AccessRights     = values[1].GetValue <int>();
                element.ScanRate         = values[2].GetValue <float>();
                element.Description      = values[3].GetValue <string>();
                element.EngineeringUnits = values[4].GetValue <string>();
                element.EuInfo           = values[5].GetValue <string[]>();
                element.EuType           = values[6].GetValue <int>();
                element.HighEU           = values[7].GetValue <double>();
                element.LowEU            = values[8].GetValue <double>();
                element.OpenLabel        = values[9].GetValue <string>();
                element.CloseLabel       = values[10].GetValue <string>();
                element.HighIR           = values[11].GetValue <double>();
                element.LowIR            = values[12].GetValue <double>();

                // check if the time zone is specified.
                if (values[13].Error >= 0)
                {
                    element.TimeZone = values[13].GetValue <int>();
                }

                // check for analog item (checks for HighEU if EuType property not supported).
                if ((values[7].Error < 0 && values[7].Error >= 0) || element.EuType == (int)OPCEUTYPE.OPC_ANALOG)
                {
                    element.ElementType = DaElementType.AnalogItem;
                }

                // check for enumerated item.
                else if (element.EuType == (int)OPCEUTYPE.OPC_ENUMERATED)
                {
                    element.ElementType = DaElementType.EnumeratedItem;
                }

                // check for digital item (checks for CloseLabel property).
                else if (values[10].Error >= 0)
                {
                    element.ElementType = DaElementType.DigitalItem;
                }
            }

            // the element must be a branch.
            else
            {
                element.ElementType = DaElementType.Branch;

                // branches could have description property.
                element.Description = values[3].GetValue <string>();
            }
        }
Beispiel #17
0
        /// <summary>
        /// Read the available non-built in properties from the server.
        /// </summary>
        /// <param name="itemId">The item id.</param>
        /// <param name="updateCache">if set to <c>true</c> the cache is updated.</param>
        /// <returns>The array of properties.</returns>
        public DaProperty[] ReadAvailableProperties(string itemId, bool updateCache)
        {
            string methodName = "IOPCItemProperties.QueryAvailableProperties";

            // query for available properties.
            int count = 0;

            IntPtr pPropertyIds  = IntPtr.Zero;
            IntPtr pDescriptions = IntPtr.Zero;
            IntPtr pDataTypes    = IntPtr.Zero;

            try
            {
                IOPCItemProperties server = BeginComCall <IOPCItemProperties>(methodName, true);

                server.QueryAvailableProperties(
                    itemId,
                    out count,
                    out pPropertyIds,
                    out pDescriptions,
                    out pDataTypes);
            }
            catch (Exception e)
            {
                if (ComUtils.IsUnknownError(e, ResultIds.E_FAIL, ResultIds.E_UNKNOWNITEMID, ResultIds.E_INVALIDITEMID))
                {
                    ComUtils.TraceComError(e, methodName);
                }

                return(null);
            }
            finally
            {
                EndComCall(methodName);
            }

            // unmarshal results.
            int[]    propertyIds  = ComUtils.GetInt32s(ref pPropertyIds, count, true);
            string[] descriptions = ComUtils.GetUnicodeStrings(ref pDescriptions, count, true);
            short[]  datatype     = ComUtils.GetInt16s(ref pDataTypes, count, true);

            List <DaProperty> properties = new List <DaProperty>();

            for (int ii = 0; ii < count; ii++)
            {
                // do not return any of the built in properties.
                if (propertyIds[ii] <= PropertyIds.TimeZone)
                {
                    continue;
                }

                DaProperty property = new DaProperty();

                property.PropertyId = propertyIds[ii];
                property.Name       = descriptions[ii];
                property.DataType   = datatype[ii];

                properties.Add(property);
            }

            // fetch the item ids.
            if (properties.Count > 0)
            {
                DaProperty[] array = properties.ToArray();

                GetPropertyItemIds(itemId, array);

                // update the cache.
                if (updateCache)
                {
                    lock (m_cache)
                    {
                        DaElement element = null;

                        if (m_cache.TryGetValue(itemId, out element))
                        {
                            element.Properties = array;
                        }
                    }
                }

                return(array);
            }

            return(null);
        }