Building Parent Tags

The structure of a parent tag is the same as any other custom tag (see Creating Custom Tag Types) with one exception: The parent tag contains a number of calls to StartTag, thereby creating child tags automatically with each new instance.

The parent tag's parameters should include all of the information that will be required by the various child tags. There must be some pattern to the child tag parameters from instance to the next for these structures to be useful. For example, if all of the child tags that perform I/O functions use addresses that can be calculated from a station number or a base address value, then the parent tag will require very little configuration. If each parameter of each child tag in each instance of the parent must be provided individually, then few benefits remain.

For parent tags with very complex configurations, you should consider creating a configuration wizard to help operators create each new instance. See The VTScada Wizard Engine. If the configuration is not overly complex, then a standard set of configuration panels should suffice.

The concepts of parent tag structure are illustrated in the following example. This shows the source code for a simple lift station with a port, a Modbus Compatible Device , a polling driver. I/O addressing in this example takes advantage of the Modbus virtual I/O feature.

{====================== Parent Tag Example =================}
{ An sample Parent tag that creates several child tags.     }
{===========================================================}
{ The parameters section of the module contains all of this }
{   tag's configuration parameters                          }
(
  Name         <:TagField("SQL_VARCHAR(64)",  "Name"     ):>   
    { Name of this tag                              };
  Area         <:TagField("SQL_VARCHAR(255)", "Area"     ):>     
    { Group which this device belongs to            };
  Description  <:TagField("SQL_VARCHAR(255)", "Description" ):>     
    { Description of device                         };
  Station   <:TagField("SQL_VARCHAR(255)", "Station"   ):> = 1 
    { Modbus address of the PLC                     };
  IOBaseAddr   <:TagField("SQL_VARCHAR(255)", "IOBaseAddr"  ):>     
    { I/O addressing offset for this instance       };
  Port         <:TagField("SQL_VARCHAR(255)", "Port"        ):>     
    { Port tag used to communicate with the device  };
  ScanInterval <:TagField("SQL_VARCHAR(255)", "ScanInterval" ):>     
    { Polling interval                              };
)
{ The variables section of the module contains the standard  }
{   variables, constants and module declarations for a tag   }
[
  Root                  { Instance of this module            };
  Value(5)              { Value of this tag                  };
  Refresh      Module   { Standard module in all tags        };
  ConfigFolder Module   { Configuration folder               };
  Common       Module   { right-click menu and tool tip      };

  [(GRAPHICS)
    Shared Number;  { the only widget that will be available }
  ]
     { Parameter Constants }
  CONSTANT #Name          = 0;
  CONSTANT #Area          = 1;
  CONSTANT #Description   = 2;
  CONSTANT #Station       = 3;
  CONSTANT #IOBaseAddr    = 4;
  CONSTANT #Port          = 5;
  CONSTANT #ScanInterval  = 6;

]

{ The initialization state of all tags will set Root to self  }
{   and call the Refresh module                               }

Init [
  If 1 Main;
  [
    Root = Self;
    Refresh();
  ]
]

Main [
{ The value of the parent tag will be taken from the value   } 
{  of the Polling Driver child-tag                           }
  Value = Variable("PollDriver\Value");
]

<
{========================== Refresh ========================}
{ This subroutine is called on startup and whenever the     }
{ tag's parameters change                                   }
{===========================================================}
Refresh
(
  Parm         { Array for parameters prior to their change };
)

Refresh [
  If 1;
  [
    { This section creates the instances of the child tags. Some      }
    { parameter values come directly from the parent (Area), some are }
    { set in code and some are built here, based on information from  }
    { the parent's parameters.                                    }
    { Since child tags are always named  ParentName\ChildName, there  }
    { is no need here to create a unique name here for each child     }
    { instance. The drivers, for example, will be Instance1\SiteDriver, }
    { Instance2\SiteDriver, etc.                                  }
 
    { Create Driver tag }
    \StartTag(Root,
              Valid(Root\Name)     { Conditional launch expression },
              "ModiconDriver"      { Tag type },
                { Name and value pairs }
              "Name",              "SiteDriver" ,  
              "Area",              Area,  { The child takes its area from the parent }
              "Description",       Concat(\GetPhrase(Description), " ", \GetPhrase("DriverLabel")),
              "Station",           Station, { from the parent configuration }
              "Channel",           0,
              "PortTag",           Port,    { from the parent }
              "TimeLimit",         1,
              "Retries",           3,
              "Hold",              0,
              "KeyOffDelay",       0,
              "Options",           0,
              "TimeSquelch",       0.2,
              "RetryTime",         1,
              "Adapter",           -1,
              "TimeInc",           2,
              "MBPRetries",        2,
              "MBPDMPathCount",    8,
              "MBPPollRate",       0.025,
              "VPLCHoldingCoils",  9999,  { Virtual I/O is used for this example }
              "VPLCInputCoils",    9999,
              "VPLCInputRegs",     9999,
              "VPLCHoldingRegs",   9999,
              "HelpKey",           Invalid);
    { Create Polling Driver tag }
    \StartTag(Root,
              Valid(Root\Name) { Conditional launch expression },
              "PollDriver"     { Tag type              },
              "Name",          "SitePoller" { Parameter name & value pairs },
              "Area",          Area,
              "Description",   Concat(\GetPhrase(Description), " Poll Driver"),
              "DeviceTag",     Concat(Name",\SiteDriver"), 
              "PollGroup",     "DEFAULT",
              "Sequence",      0,
              "Interval",      ScanInterval,
              "Offset",        0,
              "PollDisabled",  0,
              "HelpKey",       Invalid);
    { Create an Analog Status tag }
    \StartTag(Root,
              Valid(Root\Name)    { Conditional Launch expression },
              "AnalogStatus"      { Name of tag type },
              "Name",     "AS1",
              "Area",     Area,
              "Description",    "Analog Status child 1",
              "DeviceTag",    Concat(Name",\SitePoller"),
              "Address",    Cast(40000 + Cast(IOBaseAddr, 2), 4),
              "ScanRate",    1,
              "UnscaledMin",    0,
              "UnscaledMax",    4095,
              "ScaledMin",    0,
              "ScaledMax",    100,
              "Units",     "%",
              "AlarmLo",    Invalid,
              "AlarmHi",    Invalid,
              "PriorityLo",    Invalid,
              "PriorityHi",      Invalid,
              "InhibitLo",    Invalid,
              "InhibitHi",    Invalid,
              "AlarmSound",    Invalid,
              "ManualValue",    Invalid,
              "Threshold",    Invalid,
              "Questionable",    0,
              "Quality",    Invalid,
              "DisplayOrder",    Invalid,
              "HelpKey",    Invalid,
              "PopupLo",    Invalid,
              "PopupHi",    Invalid,
              "AlarmLoDeadband",  Invalid,
              "AlarmHiDeadband",  Invalid,
              "AlarmLoDelay",    Invalid,
              "AlarmHiDelay",    Invalid);
    { Create an Analog Control tag }
    \StartTag(Root,
              Valid(Root\Name) { Conditional Launch expression },
              "AnalogControl"  { Name of tag type },
              "Name",   "AC1",
              "Area",   Area,
              "Description",  "Analog Control child 1",
              "DeviceTag",  Concat(Name",\SitePoller"),
              "Address",  Cast(40000 + Cast(IOBaseAddr, 2), 4),
              "UnscaledMin",  0,
              "UnscaledMax",  4095,
              "ScaledMin",  0,
              "ScaledMax",  100,
              "Units",   "%",
              "SecurityBit",   Invalid,
              "DataSource",   Invalid,
              "Questionable",  0,
              "DisplayOrder",  Invalid,
              "HelpKey",  Invalid);
    { Create a Digital Status tag }
    \StartTag(Root,
              Valid(Root\Name) { Conditional Launch expression },
              "DigitalStatus"  { Name of tag type },
              "Name",        "DS1" { Name and value pairs },
              "Area",         Area,
              "Description",  "Digital Status child 1",
              "DeviceTag",    Concat(Name",\SitePoller"),
              "Bit0Address",  Concat(Cast(40100 + Cast(IOBaseAddr, 2), 4), "/0"),
              "Bit1Address",  Invalid,
              "ScanRate",     Invalid,
              "InvertInput",  0,
              "OffText",      "Off",
              "OnText",       "On",
              "AlarmState",   Invalid,
              "AlarmDelay",   Invalid,
              "TripOptions",  0,
              "Priority",     0,
              "Inhibit",      1,
              "AlarmSound",   Invalid,
              "ManualValue",  Invalid,
              "Questionable", 0,
              "Quality",      Invalid,
              "DisplayOrder", 1,
              "HelpKey",      Invalid,
              "PopUp",       0);
    { Create a Digital Control tag }
    \StartTag(Root,
              Valid(Root\Name)    { Conditional Launch expression },
              "DigitalControl"    { Name of tag type },
              "Name",             "DC1" { Name and value pairs },
              "Area",             Area,
              "Description",      "Digital Control child 1",
              "DeviceTag",        Concat(Name",\SitePoller"),
              "Address",          Concat(Cast(40100 + Cast(IOBaseAddr, 2), 4), "/0"),
              "InvertOutput",     0,
              "PulseDuration",    0,
              "FeedBackTag",      Invalid,
              "FeedBack1",        Invalid,
              "FeedBack0",        Invalid,
              "SecurityBit",      Invalid,
              "DataSourceTag",    Invalid,
              "InvertDataSource", 0,
              "Questionable",     0,
              "DisplayOrder",     Invalid,
              "HelpKey",          Invalid);
    Return(0);
  ]
]
{ End of Refresh }
>
 { As with all tags, the ConfigFolder module is called when the }
 { tag's  properties are displayed.                        }

<
{==================== ConfigFolder ===========================}
{ This is the shell of an editable ConfigFolder for a tag     }
{ where the ConfigFolder is created and maintained by the     }
{ ConfigFolder Wizard.                                   }
{ Manual editing of the code is allowed as long as the state  }
{ structure is maintained.                               }
{=============================================================}
ConfigFolder
(
  Parms              { Pointer to array of parameters              };
  Current            { Currently selected tab (starts at 0)        };
  PtrWaitClose       { Pointer to FLAG - TRUE when wait to close.
                         Caller must default to 0.            };
  OKPressed          { OKPressed from PropertiesDialog             };
)
[
  Constant #WIDTH = 500;
  [(1)
    IDTabLabel      = "ID";
    ConfigTabLabel  = "Config";
  ]
]
Switch [
  If Current == 0 ID;
  If Current == 1 Config;
]
ID [
  {**** If page changes, change states *****}|
  If Current != 0 Switch;
  {***** Name of the point *****}
  GUITransform(30, 90, 470, 45,
               1, 1, 1, 1, 1            { No scaling                     },
               0, 0, 1, 0               { No movement; visible; reserved },
               0, 0, 0                  { Not selectable                 },
              \DialogLibrary.PEditName());

  {***** Group (area) that the point belongs to *****}
  GUITransform(30, 200 { btm at 145 }, 470, 100,
               1, 1, 1, 1, 1            { No scaling                     },
               0, 0, 1, 0               { No movement; visible; reserved },
               0, 0, 0                  { Not selectable                 },
               \DialogLibrary.PAreaSelect(1 {can edit}, 2 {ID}));
  {***** Description of the point *****}
  GUITransform(30, 200, 470, 155,
               1, 1, 1, 1, 1            { No scaling                     },
               0, 0, 1, 0               { No movement; visible; reserved },
               0, 0, 0                  { Not selectable                 },
               \DialogLibrary.PEditField(2, \DescriptionLabel, 4 {text},
                                         3 {ID}));
]

Config [
    {**** If page changes, change states *****}
  If Current != 1 Switch;
    {***** Communications Port *****}
  GUITransform(30, 90, #Width - 30, 45,
               1, 1, 1, 1, 1,
               0, 0, 1, 0,
               0, 0, 0,
               \DialogLibrary.PSelectObject(#Port    { Parm          },
                                            "Ports"  { Point type    },
                                            "Communications Port" { Title },
                                            1        { Focus ID      },
                                            0        { List at top   },
                                            0        { Align top of bevel )));
    {***** Station Address *****}
  GUITransform(30, 145, #Width / 2 - 10, 100,
               1, 1, 1, 1, 1            { No scaling                     },
               0, 0, 1, 0               { No movement; visible; reserved },
               0, 0, 0                  { Not selectable                 },
               \DialogLibrary.PEditField(#Station  { Parm Num            },
                                         "Station Address" { Title       },
                                         1              { Short          },
                                         2              { Focus ID       }));
    {***** I/O Base Address *****}
  GUITransform(30, 200, #Width / 2 - 10, 155,
               1, 1, 1, 1, 1            { No scaling                     },
               0, 0, 1, 0               { No movement; visible; reserved },
               0, 0, 0                  { Not selectable                 },
               \DialogLibrary.PEditField(#IOBaseAddr  { Parm Num         },
                                         "I/O Base Address" { Title      },
                                         1              { Short          },
                                         2              { Focus ID       }));
    {***** Scan Interval *****}
  GUITransform(#Width / 2 + 10, 145, #Width - 30, 100,
               1, 1, 1, 1, 1            { No scaling                     },
               0, 0, 1, 0               { No movement; visible; reserved },
               0, 0, 0                  { Not selectable                 },
               \DialogLibrary.PEditField(#ScanInterval { Parm Num        },
                                         "Scan Interval" { Title         },
                                         3              { Float          },
                                         3              { Focus ID       }));
]
{ End of ConfigFolder }
>

<
{========================= Common ==============================}
{ This module handles the common actions associated with all    }
{ drawing modules for this point. It will be called by all     }
{ external drawing modules.                                }
{===============================================================}
Common
(
  Left          { Area occupied by the drawing object         };
  Bottom;
  Right;
  Top;
)

Common [
  \PostIt(Left, Bottom, Right, Top, \Name, \Description,
          ! \GetUserSession()\NavActive);
  \Navigator(1 { Opening condition for the folder },
             Left, Bottom, Right, Top    { Target area for opening -
                                          same as the GUI statement
                                          area.     },
             { Menu line 1 } \PropertiesLabel, Invalid, 0, Invalid);
]
{ End of Common }
>