Hierarchical Custom Controller Browsing

Browsing Custom Controller in a Hierarchy

About this task

Hierarchical browsing can be developed using the Collector Toolkit. Hierarchical browsing enables you to browse custom collectors in a hierarchical manner if your server supports hierarchical organization of tags in a tree structure.

To browse for Custom Collector tags in a hierarchy:

Procedure

  1. Browse your data source for new tags.
  2. From the Collector list, select the custom collector you wish to browse. A hierarchical tree appears in the Browse Results window.
  3. To limit the displayed tags to only those that are not collected, from the Show Only list select Source Tags Not Collected.
  4. To limit the displayed tags to match a tag name or tag description, enter the value to match in the Source Tag Name or Description text boxes.
  5. Navigate to the node in the tree you want to browse, and then select Browse. The tags within the selected portion of the Custom Collector tag hierarchy will be displayed.
    1. To browse automatically, select the AutoBrowse check box. The available tags will be displayed in the Browse Results window whenever a node is selected in the tree.
    2. To show all child elements within a hierarchy, enable the Show All Children check box. All tags at, or below the hierarchical level of the selected node in the tree will be displayed in the Browse Results window.
  6. Select the tag or tags you want to add to Historian, and select Add Selected Tags. Collected tags will appear in black in the tag list.
    Note:
    • The Browse option for the Custom Collector will not return all items that reside in the Server configuration. Items that may not get returned include, but are not limited to, unsupported data types and user-defined items in some Simulation Servers. Occasionally, items that do not appear in the browse can still be added manually using the Add Tag Manually option.

    • If you are browsing and adding tags with the Custom Collector, note that some Custom Collectors do not support data blocks with a length greater than 1. These Servers can choose to show the first item in an array in the browse rather than show them all. For example, an OPC Server may contain 3000 analog values datablock:1 to datablock:3000, but would only show datablock:1.
    • If you want to archive data from poll records of a length greater than 1, it is recommended that you use the Excel Add-In to configure a large block of tags - including the missing items - and add the tags.
    • If you are unable to browse items on your server containing forward slashes ( / ), you may have to change the default separator in your Custom Collector configuration. To do so, you will need to open the Windows Registry Editor and edit the HKEY_LOCAL_MACHINE\SOFTWARE\Intellution, Inc\iHistorian\Services\[Collector Name]\OPCBrowseTreeSep key (where [Collector Name] is the name of your Custom collector) and change the string value to a character not present among your Server item IDs. Typical values include |, !, or &. Create this key, if it does not already exist.
    • If you are unable to browse readable items in your server with the Custom collector, you may need to change the browse access mask used by the collector. To do so, you will need to open the Windows Registry Editor and edit the [HKEY_LOCAL_MACHINE\SOFTWARE\Intellution, Inc.\iHistorian\Services\[Collector Name] (where [Collector Name] is the name of your Custom collector) and add DWORD key "OPCBrowseAccessRightsMask"=dword:00000003. Valid values are 0, 1, 2, 3 with 1 being the default. Use 0 or 3 if you are unable to browse readable items. Creating or changing the value takes effect on the next browse attempt and does not require a collector restart.

Developing Hierarchical Browsing using Collector Toolkit

The following callback is used to develop hierarchical browsing using the Collector Toolkit:

ihCollectorToolkitGetTagsHierarchical(wchar_t* BrowsePosition, wchar_t* NodeFilter, 
ihTKHierarchicalBrowseResponse* Response);

The following are the parameters in this callback:

NameDescription
BrowsePositionThe place where the current node is selected.
NodeFilterThe delimiter using which hierarchical node differentiation is made
Response structureContains the number of nodes/leaves for a particular node.

To define the hierarchical tree format as illustrated in the Add Multiple Tags from Collector figure above, Historian provides the following information in three responses, provided as a sample here. Use the information in the following three responses to browse the tags and add the required tags.

As a first response Historian returns:

ihInterfaceGetTagsHierarchical()

The following are the parameter values:

BrowsePosition "ihHierarchicalBrOwSeRoOt" 
NodeFilter: "" (By default, this uses a ???/???) 
Response = NULL
NodeCount 1 int
NodeNames "_Status" wchar_t *
FullNodeNames "/_Status" wchar_t *
ihInterfaceGetTags()
BrowsePosition ???ihHierarchicalBrOwSeRoOt wchar_t*

Second response:

ihInterfaceGetTagsHierarchical() 
BrowsePosition "/_Status" 
NodeNames "DA_Server"
FullNodeNames "/_Status/DA_Server" 
ihInterfaceGetTags()
BrowsePosition ???/_Status???

Third response:

ihInterfaceGetTagsHierarchical() 
BrowsePosition "/_Status/DA_Server" 
BrowsePosition "/_Status/DA_Server"

Collector Initialization Callbacks

The following are the callbacks used for a collector when it is initialized:

  • ihCollectorToolkitPreInitialize
  • ihCollectorToolkitInitialize
  • ihCollectorToolkitInitializeCompleted

When a collector is shutting down, ihCollectorToolkitShutdown method is called and the collector performs all the necessary steps before it is completely shut down.

Example

The following sample program helps you understand the ihCollectorToolkitPreInitialize function.

Historian expects Custom Collector Pre-Initialization information from the user. For example, custom collector can browse tags, collector type and so on.

/// </summary>
/// <param name="PreCfg">Collector Pre - Configuration information structure</param>
/// <param name="Cfg">Collector Configuration information structure</param>
void RandomValueSimulator::ihCollectorToolkitPreInitialize(ihInterfaceTKPreCfgInfo *PreCfg, ihInterfaceTKCfgInfo *Cfg)
{
// Initializes General1-5 values, here General1,2 were initialized with 1000, 60.
Cfg->CustomProp1 = TKStrdup(_T("1000"));
Cfg->CustomProp2 = TKStrdup(_T("60"));
// Historian follows representation of the collectors in the form of ComputerName_CollectorName, Tags in the form of ComputerName.TagName. 
// In the following section custom collector is trying to get the computer name.
CString ComputerName, IP;
TKGetHostNameAndIP(ComputerName, IP);
if (ComputerName.GetLength() >; 0)
{
ComputerName.Append(_T("."));
wcscpy_s(Cfg->;DefaultTagPrefix, ComputerName.GetBuffer());
}
RandPreCfg = *PreCfg;
RandPreCfg.CanSendOPCQuality = TRUE; // to send OPC Quality
RandPreCfg.InterfaceType = ihTKCustom; // interface type
RandPreCfg.MultipleInstancesAllowed = FALSE; 
RandPreCfg.MinimumInterval = RandMinimumInterval; 
RandPreCfg.MaxTagsPerRead = MaxTagsPerGroup;
RandPreCfg.CanReadASync = TRUE;// to read tags Asynchronously
RandPreCfg.CanBrowseSource = TRUE;// you can browse the collector
RandPreCfg.CanSourceTimestamp = TRUE;// collector sends data containing source time stamp or server time stamp
RandPreCfg.ForceInputScaling = FALSE;
RandPreCfg.NeedMsgPump = FALSE;
RandPreCfg.ForcedScaleLO = 0.0; // Low engineering unit
RandPreCfg.ForcedScaleHI = (float) RAND_MAX;// High engineering unit
RandPreCfg.DoesReloadMode = FALSE;
RandPreCfg.DoesLagTimes = FALSE;
RandPreCfg.CanBrowseHierarchical = TRUE;
*PreCfg = RandPreCfg;
}

For the ihCollectorToolkitInitialize function:

Collector Initialization
/// </summary>
/// <param name="Cfg">Collector Configuration information</param>
/// <param name="PreCfg">Collector Pre-Configuration Information</param>
/// <param name="ErrorMsg">Error Message while initializing</param>
/// <param name="ErrorMsgSize">Size of Error Message</param>
/// <param name="RegKeyName">modification required registry keys, if any </param>
/// <param name="Callbacks">Callbacks of all the asynchronous methods</param>
/// <param name="DoDebug">debug param</param>
/// <returns>status of the method call</returns>
int RandomValueSimulator::ihCollectorToolkitInitialize(ihInterfaceTKCfgInfo *Cfg, 
ihInterfaceTKPreCfgInfo *PreCfg, wchar_t *ErrorMsg, int ErrorMsgSize, wchar_t *RegKeyName, ihCollectorToolkitCallback *Callbacks, int DoDebug)
{
// updates configuration information to collector
ihCollectorToolkitPropertyUpdate(Cfg);
TKFree(TagPrefix);
// Historian follows representation of Tags in the form of ComputerName.TagName.
TagPrefix = TKStrdup(Cfg->DefaultTagPrefix);
Cfg->DoOnFly = 1;
// Initializes error msg to ""(NULL)
ErrorMsg = TKStrdup(_T(""));
srand((unsigned) time(NULL));
RandCfg = *Cfg;
g_Callbacks = Callbacks;
return(TRUE);
}

Polled Tag Callbacks

For polled tags we use different callback functions at various stages.

Step 1
At collector start up, ihCollectorToolkitPolledInit and ihCollectorToolkitPolledInitCompleted callbacks are triggered and the details of all the Polled tags for the given collector is returned.
Step 2
If a polled tag is added, ihCollectorToolkitPolledAddTag method is called and the details of the tag is entered in the collector tag list.
Step 3
If a polled tag is deleted, ihCollectorToolkitPolledDeleteTag method is called and the tag details are removed from the collector tag list.

Example

The following sample helps you understand the given callback functions.

The collectors maintains the polled tag details in the local Cache as:

map<int, ihInterfaceTKPolledTagInfo> TagIdToTagInfoMap;

This map is filled using this sample example:

/// Polled tags initialization started
/// </summary>
/// <returns>TRUE/FALSE</returns>
int RandomValueSimulator::ihCollectorToolkitPolledInit(void)
{
TagIdToTagInfoMap.clear();
return CCollectorDelegator::ihCollectorToolkitPolledInit();
}
??
---------------------------------------------------------------------
??
/// <summary>
/// Historian updating the source saying that, polled tags initialization successful. 
Are there any initialization from source for polled tags? 
/// <summary>
/// <returns>TRUE/FALSE</returns>
int RandomValueSimulator::ihCollectorToolkitPolledInitCompleted(void)
{
return CCollectorDelegator::ihCollectorToolkitPolledInitCompleted();
}
??
---------------------------------------------------------------------
??
/// <summary>
/// Adds polled tag to the historian from source
/// </summary>
/// <param name="PolledTag">ihInterfaceTKPolledTagInfo instance</param>
/// <param name="IsCollectorStarting">collector status</param>
/// <returns>TRUE/FALSE</returns>
int RandomValueSimulator::ihCollectorToolkitPolledAddTag(ihInterfaceTKPolledTagInfo 
*PolledTag, int IsCollectorStarting)
{
TagIdToTagInfoMap.insert(std::pair<int, ihInterfaceTKPolledTagInfo>
(PolledTag-<TagId, *PolledTag));
return CCollectorDelegator::ihCollectorToolkitPolledAddTag(PolledTag, 
IsCollectorStarting);
}
??
-----------------------------------------------------------------------
??
/// <summary>
/// Deleted polled tag from Historian from Client Tools/non web admin/external tools
/// <summary>
/// <param name="tagId">Tag Identification</param>
/// <returns>TRUE/FALSE</returns>
int RandomValueSimulator::ihCollectorToolkitPolledDeleteTag(int tagId)
{
TagIdToTagInfoMap.erase(TagIdToTagInfoMap.find(tagId));
return CCollectorDelegator::ihCollectorToolkitPolledDeleteTag(tagId);
}

Unsolicited Tags Callbacks

For unsolicited tags we use different callback functions at various stages.

Step 1
At collector start up, ihCollectorToolkitASyncInit and ihCollectorToolkitASyncInitCompleted callbacks are triggered and the details of all the unsolicited tags for the given collector is returned.
Step 2
If an unsolicited tag is added, ihCollectorToolkitASyncAddTag method is called and the details of the tag is entered in the collector tag list.
Step 3
If an unsolicited tag is deleted, ihCollectorToolkitASyncDeleteTag method is called and the tag details are removed from the collector tag list.

Example

The following sample helps you understand the given callback functions. The below sample code creates a thread for simulating the Unsolicited tag's collector behavior.

//This structure is the list of Unsolicited Tags. 
struct AsyncTagList : public CList<ihInterfaceTKASyncTagInfo*, ihInterfaceTKASyncTagInfo*>
{
virtual ~AsyncTagList()
{
FreeAll();
} 
void AddTag(ihInterfaceTKASyncTagInfo* tag)
{
ihInterfaceTKASyncTagInfo* info = new ihInterfaceTKASyncTagInfo;//need to allocate in delegator
memcpy(info, tag, sizeof(ihInterfaceTKASyncTagInfo));
AddTail(info);
}
void FreeAll()
{
while (!IsEmpty())
delete RemoveHead();
}
}; 
AsyncTagList g_AsyncTags; 
??
----------------------------------------------------------------------------
??
/// <summary>
/// Initializes unsolicited tags, by creating dedicated thread
/// <summary>
/// <returns></returns>
int ??????RandomValueSimulator::ihCollectorToolkitASyncInit(void)
{
??????????????CSingleLock lock(&g_Sync, TRUE);
??????????????g_AsyncTags.FreeAll();
??????????????if (!g_AsyncThread)
????????????????????????????g_AsyncThread = AfxBeginThread(TKAsyncReadFunc, this);
??????????????return TRUE;
}
??
----------------------------------------------------------------------------
??
/// <summary>
/// unsolicited tags initialization completed
/// <summary>
/// <returns>TRUE/FALSE</returns>
int ??????RandomValueSimulator::ihCollectorToolkitASyncInitCompleted(void)
{
??????????????return TRUE;
}
??
------------------------------------------------------------------------------
??
/// <summary>
/// Custom Collector Initialization completed and is ready to read data from Source for unsolicited tag 
/// <summary>
/// <returns>TRUE/FALSE</returns>
int ??????RandomValueSimulator::ihCollectorToolkitASyncStartReading(void)
{
??????????????InterlockedExchange(&g_DoAsyncRead, TRUE);
??????????????return TRUE;
}
??
------------------------------------------------------------------------------
??
/// <summary>
/// Adds unsolicited tag to the historian
/// <summary>
/// <param name="ASyncTag">ihInterfaceTKAsyncTagInfo pointer</param>
/// <param name="IsCollectorStarting">Current Status of the Collector</param>
/// <returns>TRUE/FALSE</returns>
int ??????RandomValueSimulator::ihCollectorToolkitASyncAddTag(ihInterfaceTKASyncTagInfo *ASyncTag, int IsCollectorStarting)
{
??????????????CSingleLock lock(&g_Sync, TRUE);
??????????????g_AsyncTags.AddTag(ASyncTag);
??????????????return TRUE;
}
??
-------------------------------------------------------------------------------
??
/// <summary>
/// From Clients tools/non-web admin/custom tools, if user deletes a tag, historian updates custom collector saying that tag got deleted.
/// So that, custom collector stops collecting data from source for that tag
/// <summary>
/// <param name="tagId">Tag Identifier</param>
/// <returns>TRUE/FALSE</returns>
int ??????RandomValueSimulator::ihCollectorToolkitASyncDeleteTag(int tagId)
{
??????????????CSingleLock lock(&g_Sync, TRUE);
??????????????POSITION pos = g_AsyncTags.GetHeadPosition();
??????????????while (pos)
??????????????{
????????????????????????????ihInterfaceTKASyncTagInfo * tagInfo = g_AsyncTags.GetAt(pos);
????????????????????????????if (tagInfo->TagId == tagId)
????????????????????????????{
??????????????????????????????????????????g_AsyncTags.RemoveAt(pos);
??????????????????????????????????????????return TRUE;
????????????????????????????} ??????????????
????????????????????????????g_AsyncTags.GetNext(pos);
??????????????}
??????????????return TRUE;
}
??
--------------------------------------------------------------------------------
??
/// <summary>
/// This method called by historian, if it needs to perform any calculations on source data. This method is only usefull in "Calculation collector" way of collection for unsolicited tags
/// <summary>
/// <param name="StartTime"> start time for reload</param>
/// <param name="EndTime"> end time for reload</param>
/// <returns>TRUE/FALSE</returns>
int RandomValueSimulator::ihCollectorToolkitASyncReload(ihTKTimeStruct *StartTime, ihTKTimeStruct *EndTime)
{
??????????????return CCollectorDelegator::ihCollectorToolkitASyncReload(StartTime, EndTime);
}
??
-------------------------------------------------------------------------------
??
/// <summary>
/// Unsolicited dedicated thread corresponding method. Here all unsolicited tags get data from source(usually, way should be source notification to historian) and sends to historian
/// <summary>
/// <param name="param"> Collector instance </param>
UINT RandomValueSimulator::TKAsyncReadFunc(void* param)
{
??????????????POSITION pos = NULL;
??????????????RandomValueSimulator* pColl = (RandomValueSimulator*) param;
??????????????while (TRUE)
??????????????{
????????????????????????????if (g_DoAsyncRead)
????????????????????????????{
??????????????????????????????????????????CSingleLock lock(&g_Sync, TRUE);
??????????????????????????????????????????if (!g_AsyncTags.IsEmpty())
??????????????????????????????????????????{
??????????????????????????????????????????????????????// gets each unsolicited tag into ihInterfaceTKAsyncTagInfo object
??????????????????????????????????????????????????????if (!pos) 
????????????????????????????????????????????????????????????????????pos = g_AsyncTags.GetHeadPosition();
??????????????????????????????????????????????????????ihInterfaceTKASyncTagInfo* tag = g_AsyncTags.GetNext(pos);
??????????????????????????????????????????????????????int numTags = 1;
??????????????????????????????????????????????????????ihInterfaceTKDataInfo data;
??????????????????????????????????????????????????????memset(&data, 0, sizeof(ihInterfaceTKDataInfo));
??????????????????????????????????????????????????????data.Tag = pColl->TKStrdup(tag->Tag);
??????????????????????????????????????????????????????data.DataProp.ValueDataType = tag->DataType; ????????????????????????????????????????????????
??????????????????????????????????????????????????????data.DataProp.TimeStamp = pColl->TKGetSystemTime();
??????????????????????????????????????????????????????// gets data for selected tag
??????????????????????????????????????????????????????pColl->ihCollectorToolkitGetData(0, 0, 0, numTags, NULL, &data);
??????????????????????????????????????????????????????unsigned long collectionTime = (unsigned long)time(0);
??????????????????????????????????????????????????????int tagId = tag->TagId;
????????????????????????????????????????????????????????ihInterfaceTKASyncData asyncData;
??????????????????????????????????????????????????????memset(&asyncData, 0, sizeof(ihInterfaceTKASyncData));
??????????????????????????????????????????????????????asyncData.NumValues = numTags;
??????????????????????????????????????????????????????asyncData.TagIds = &tagId;
??????????????????????????????????????????????????????asyncData.Values = &data.DataProp;
??????????????????????????????????????????????????????asyncData.CollectionTimes = &collectionTime;
??????????????????????????????????????????????????????// sends to historian
??????????????????????????????????????????????????????pColl->ihCollectorToolkitDataCallback(&asyncData);
??????????????????????????????????????????}
??????????????????????????????????????????else
??????????????????????????????????????????{
??????????????????????????????????????????????????????pos = NULL;
??????????????????????????????????????????}
????????????????????????????}
????????????????????????????else
????????????????????????????{
??????????????????????????????????????????pos = NULL;
????????????????????????????}
????????????????????????????long sleepTimeInMs = 500 + (rand() % 46) * 100; // sleep from 0.5 to 5 seconds
????????????????????????????Sleep(sleepTimeInMs);
??????????????}
??????????????return 0;
}