The UDT Manager is opened with the UDT Definitions button in the Asset Manager toolbar.
The UDT Manager configuration page defines reusable type templates called UDT definitions. A UDT definition describes one type of equipment, for example a pump or a power meter, as a set of variable members, alarm members, and event members. The member fields can contain tokens written as {TokenName} that are filled in per instance.
A UDT instance is a definition together with a set of parameter values. Creating an instance writes real variables into the variable tables and real alarms and events into the Alarms and Events tables. Every variable member states the device it is created on, so one instance can cover several devices and several protocols at once: the control points of a pump on a PLC, its motor current on a power meter, and its setpoint as a Local variable, from one instance of one type. These rows behave like any other variable, alarm, or event in the runtime, the Historian, and the viewers. The rows are marked as managed by their instance and can only be changed through the UDT Manager.
Editing a definition never changes existing instances by itself. Instances whose synchronized version differs from the definition version are shown as Outdated, and the changes reach them only through an explicit push with a diff preview. This keeps a definition edit from silently rewriting a running plant.
UDT Definitions
The UDT Manager page lists all definitions with their member counts, instance count, version, and status. Create a new definition with the Add button. Press the Settings icon on a definition row to open the definition editor, the Instances icon to open the instance list of the definition, and the Delete icon to remove it. A definition that still has instances is kept until its instances are deleted or detached.
The definition editor contains the Name and Description fields and five tabs: Parameters, Variables, Alarms, Events, and Symbol. The version shown on the definition is a content hash that changes on every save. Instances record the version they were last pushed with, which is how the Outdated status is detected.
| Setting | Description | Example |
|---|---|---|
| Name | Unique name of the definition. | Pump |
| Description | Free text description. | Centrifugal pump, S7 data block layout |
No rows match the filter.
The Parameters, Variables, Alarms, and Events tabs share the same row tools. Each row starts with a drag handle: drag a row by its handle to change the order of the members, which is the order they are listed in and created in. The order is display only and never changes whether an instance is Synced or Outdated.
Each tab has its own Export JSON, Export CSV, and Import buttons for the rows of that tab. Export is disabled while the list is empty. Import accepts a JSON or a CSV file and merges it into the list: rows are matched by Name for Parameters and Variables and by Condition for Alarms and Events, matching rows are updated, and new rows are appended. The CSV delimiter follows the CSVDelimiter parameter on the Parameters page. Imported rows are only in the editor until Save is pressed, so an unwanted import is undone with Cancel.
Parameters
Parameters are the values that differ per instance. A parameter is referenced in member fields as {ParameterName}. When an instance does not supply a value, the default value of the parameter is used. Defaults are resolved at push time, so editing a default reaches every inheriting instance on its next push.
A parameter has the type Text, Number, or Device. A Device parameter holds the name of a device. Its value is typed, both in the definition and in the Add Instance dialog, because a device name can be composed from other parameters and a definition is often written before the devices it will point at exist. Variable members point at a Device parameter to state where they are created, which is why an instance carries its devices as parameter values.
A definition normally declares one Device parameter named Device, which is the parameter the token {Device} refers to and the value the Device column of a variable member defaults to. Declare further Device parameters, for example PowerMeter, to create part of the instance on another device.
The built-in tokens {Instance}, {Asset}, and {Index} are always available and resolve to the instance name, the asset path of the instance, and the element number of an array member. They cannot be used as parameter names.
| Setting | Description | Example |
|---|---|---|
| Name | Token name, referenced in members as {Name}. | HighSpeedLimit |
| Label | Display label shown in the instance dialog. | High speed alarm limit |
| Type | Text, Number, or Device. Number values must be numeric. A Device value is the name of a device and is selected from a list. | Device |
| Default value | Value used when an instance does not supply the parameter. The default of a Device parameter is typed, like every other default. | 2900 |
No rows match the filter.
Variable Members
Each variable member becomes one variable per instance. The Name field and the address fields of every protocol group accept tokens. Member names are free-form, and every token in a member name resolves through the definition parameters and the built-in tokens. A member name typically embeds its device through the Device parameter it materializes on, for example {Plc}.{Instance}_Speed for a member whose device comes from a Plc parameter. The materialized name is the fully resolved text. After a device is renamed, set the Device parameter of the affected instances to the new name and push them; the push renames the variables, conditions, and messages together. After token substitution, the result is validated against the protocol of the device the member is created on, so an S7 instance requires a valid S7 address and a Modbus TCP instance requires a valid Modbus address. Part of that check already runs when the definition is saved. An address or object type that contains no token is checked in full, and for a Modbus TCP address the fixed part in front of the first token is checked as well, so a wrong register prefix such as %MC is reported on the definition itself instead of on every instance that materializes it.
When one device carries several instances of the same definition, include {Instance} in the member name, for example {Instance}_Speed, so the materialized names stay unique. The materialized variables are referenced everywhere else by their resolved name, for example "PLC1.Pump1_Speed".
Each variable member holds one group of settings per protocol. A group contains exactly what that protocol needs, for example a data type and an address for Modbus TCP, a node id for OPC UA, or an object type and an index for DNP3. When an instance is created, the group that matches the protocol of the member's own device is used and the other groups are left alone. Two members of the same definition can therefore use different groups in the same instance. A group that is filled in for a protocol you never instantiate on simply stays unused.
The member row holds the settings you scan the list by: Enable, Name, Device, Units and Historian. The expander in front of the member name opens the member details, which hold the remaining common settings grouped as General, Instantiation, Array, Scaling, Historian and Gateway, followed by one group per protocol. In the details, the User groups, Converter, Sample group and Trend groups fields each offer a selection button that opens the same picker used on the variable configuration pages. A protocol group opens automatically when the member already uses it and shows a check mark, so the protocols a member is built for are visible at a glance.
The data type and the object type of a group are selected from a list that matches the protocol. The lists differ per protocol because the protocols differ: an S7 instance offers no LINT or ULINT, a MELSEC instance no BYTE, LINT, ULINT or LREAL, an Omron FINS instance no BYTE, WORD, DWORD, LINT or ULINT, and DNP3, BACnet and IEC104 offer their own object types instead of a data type. The address, index, information object address and object instance accept tokens, so the structure of a point stays in the definition while its position stays per instance. One definition can therefore serve two protocols: a breaker member can be Single bit binary input(1-1) on DNP3 and Single point (M_SP) on IEC104, both using the same {PointIndex} parameter.
A setting with a protocol default may stay empty. The field shows that default as its placeholder, and the corresponding field on the materialized variable stays editable after a Detach. A setting filled in is applied to every instance and kept up to date by a Push, like any other member setting. When a group is missing a setting the protocol requires, creating or pushing the instance reports which protocol and which setting is incomplete, and nothing is written.
The Device column of a variable member states the device the variable is created on. Leave it at {Device} to follow the Device parameter of the instance, enter another Device parameter such as {PowerMeter} to create the variable on a second device, enter a device name to fix the variable on that device, or enter Local to create it as a Local variable. An empty field is read as {Device}. The field is typed in full. A device name can also be composed from several parameters, for example PLC{Line}{Machine}, so the field offers no list of devices to choose from.
Device names are unique across all protocols, so the device also determines the protocol, and with it which protocol group of the member is used. This is what lets one instance cover several devices and several protocols at the same time.
When the Device of a member cannot be resolved, for example because the instance leaves the parameter empty or the device no longer exists, the member is reported and nothing is written. The existing variables of the instance are kept as they are until the Device is corrected and the instance is pushed again, and the corrected member is reconnected to its own rows rather than creating new ones. On a Local member, External Source marks the materialized Local variables as External Source variables: a Python script with Network & File Access can only write those, and each counts as a license point. In this version they are counted but not yet part of the license check, see the Local Variables article. The push applies it to every instance.
The Gateway group holds OPC UA, Writeable, Modbus Address, MQTT Publisher, Kafka Device, Kafka Remap Device and Kafka Remap Variable, the settings that expose the materialized variable to other systems. They apply whatever protocol the device of the member uses, and Modbus Address, MQTT Publisher and the three Kafka fields accept tokens. Writeable is the write permission of the gateways, shared by the OPC UA Server, the Modbus TCP and Modbus RTU servers, MQTT publishers and Kafka devices.
The Kafka Producer and Kafka Consumer settings of earlier versions are replaced: a member with a Kafka Device is always produced, and consumed Kafka messages write the materialized variable when the Kafka device has Consumer on and the member has Writeable ticked. A variable member file of an earlier version that still carries Kafka Producer and Kafka Consumer is converted when it is imported on the Variables tab, with the rules described in the Kafka Devices article.
| Setting | Description | Example |
|---|---|---|
| Enable | Enables or disables the materialized variable. | true |
| Include when | Decides whether this member is created for an instance at all. Leave empty to always create it. A parameter such as {HasVFD}, or true, false, 1 or 0, decides per instance. | {HasVFD} |
| Name | Tokenized variable name. | {Instance}_Speed |
| Device | Device the variable is created on. A Device parameter, a device name, or Local. Empty is read as {Device}. | {Device} |
| Units | Engineering units. | rpm |
| Format | Display format of the materialized variables, a pattern such as 0.00 or a parameter token such as {Fmt} so each instance can carry its own. See Format in the variable articles. | 0.00 |
| Min Raw / Max Raw / Min Scaled / Max Scaled | Engineering scaling of the raw value. | 0 / 3000 / 0 / 3000 |
| Deadband | Historian deadband of the materialized variable. | 0.5 |
| Historian | Enables history recording for the materialized variable. | true |
| Round to Format | Stores the historian samples of the materialized variables rounded to the decimals of their Format. Owned by the definition like Historian. See Round to Format in the variable articles. | false |
| History Source | History Source of the materialized variables: empty (Project default), Historian (EnvisionSCADA Historian), the name of a History Source or a parameter token. Owned by the definition like Historian. See the History Sources article. | PlantPI |
| History Reference | History Reference of the materialized variables. Empty uses the History Reference of the source. A parameter token such as {PiTag} gives each instance its own reference; in an array member, {Index} gives each element its own reference. | {PiTag} |
| History Parameters | History Parameters of the materialized variables, key=value pairs separated by ;, added to the History Parameters of the source. Accepts parameter tokens. | scale=0.1 |
| Writeable | Allows external systems to write the materialized variable through the gateways: the OPC UA Server, the Modbus TCP and Modbus RTU servers, an MQTT publisher with Subscribe on, and a Kafka device with Consumer on. Writes from pages, actions, scripts and the API are independent of this setting. | false |
| Description | Tokenized description of the materialized variable. | {Instance} speed feedback |
| UserGroups | User groups that may access the materialized variable. | Operators |
| Signature Required | Requires an electronic signature for operator writes to the materialized variables while Part 11 compliance mode requires one for variable writes. Owned by the definition like Writeable and applied to every instance by Push; an instance takes it over with Override. A member added while Part 11 compliance mode is enabled starts selected. See the Electronic Signatures article. | true |
| Converter | Converter applied to the materialized variable. | |
| OPC UA | Exposes the materialized variable on the OPC UA server. When Historize is also ticked, the Historian data of the variable is readable through OPC UA Historical Access. | true |
| MinimumTime (seconds) | Minimum time between two history records of the materialized variable. | 0 |
| SampleGroup | Sample group that determines how often the materialized variable is read. | Fast |
| Trend Groups | Trend groups the materialized variable belongs to. | Pumps |
| MQTT Publisher | MQTT publisher the materialized variable is published by. Accepts tokens, for example {Broker}, so the instances of one definition can be published by different publishers. Leave empty to keep the variable unpublished. A push reports an instance whose resolved value names no existing MQTT publisher, and nothing is written for that instance. | {Broker} |
| Kafka Device | Kafka device the materialized variable is linked to. A linked variable is always produced to the Producer Topic of the Kafka device, and consumed messages write it when the Kafka device has Consumer on and Writeable is ticked. Accepts tokens. Leave empty to keep the variable unlinked. | KafkaBroker1 |
| Kafka Remap Device / Kafka Remap Variable | Device name and variable name used for the materialized variable in Kafka messages instead of its own device name and variable name. Accept tokens. | Line1 / {Instance}_Speed |
| Protocol group | Settings | Example |
| Modbus TCP | Type, Address | REAL, %MW{BaseAddress} |
| EtherNet/IP | Type, Address | REAL |
| S7 | Type, Address | REAL, DB{DbNumber}.DBD0 |
| MELSEC | Type, Address | REAL, D{BaseAddress} |
| OPC UA | Type, Address, Sampling Interval, Subscription | REAL, ns=2;s={Instance}_Speed, 250, Subscription 1 |
| DNP3 | Type (object type), Address (index), Pulse | 32 bit Analogue input(30-1), {PointIndex}, false |
| BACnet | Type (object type), Address (object instance), Write Priority | analog-input, {PointIndex}, 8 |
| IEC104 | Type (ASDU type), Address (information object address), Pulse | Measured short float (M_ME_NC), {Ioa}, false |
| IEC61850 | Type, Address (object reference), Command Mode, Command Checks | REAL, {Ied}LD0/MMXU1.TotW.mag.f[MX], Automatic, Interlock check |
| MQTT | Type, Subscribe Topic, Publish Topic. On a device with Protocol Sparkplug B: Type, Sparkplug Device ID, Metric Name | REAL, plant/{Instance}/speed. Sparkplug B: REAL, Line1, {Instance}/Speed |
| Local | Type, External Source | REAL, false |
No rows match the filter.
Array Members
A variable member can expand into several variables by setting Array size in the member details. Array size accepts a number or a parameter, for example {StageCount}, so each instance can size the same array through its parameter values. First index sets the value of {Index} for the first element and defaults to 0. The member name must contain the built-in {Index} token, which resolves per element to First index plus the element number, for example {Device}.{Instance}_StageTemp[{Index}]. The materialized variables are ordinary variables and are referenced by their resolved name, for example "PLC1.OvenA_StageTemp[3]".
Addresses can calculate per element with a compute expression {= ... }. The expression is evaluated after the tokens are resolved and accepts numbers and + - * / ( ), for example %MW{= {BaseAddress} + {Index} * 2 } for a REAL array on consecutive Modbus TCP registers. On DNP3, BACnet and IEC104 the index, object instance or information object address can simply be {Index}, with First index acting as the base value. On OPC UA the Node id may be written in two forms: with {Index} it addresses one element node per element on a server that exposes element nodes, and without {Index} it addresses one array-valued node, from which every element reads the value at its own position and to which an element write addresses only that position. Both forms use the same Array size and First index settings.
Alarm members and event members expand the same way: set Array size on the member and include {Index} in the condition, for example {Device}.{Instance}_StageTemp[{Index}] > {HighTemp}. One alarm or event is materialized per element, each with its own message and actions.
When the resolved size changes, a Push adds or removes elements at the end. Existing elements keep their identity, including history recording. The push preview folds identical element changes into one line with an element count, and the resolved instance detail shows an array member as one row with the index range and an element count.
Conditional Members
One definition can cover the variants of a unit. The Include when setting in the Instantiation group of a member decides whether that member is created for an instance at all. Leave it empty and the member is always created. Enter a parameter such as {HasVFD}, or a fixed true, false, 1 or 0, and each instance decides for itself. An instance that excludes a member has no variable, alarm or event row for it, so a pump type with an optional variable speed drive and an optional heater is one definition with two Text parameters rather than four definitions.
Include when is resolved per instance, like every other member setting, and the resolved value must be exactly true, false, 1 or 0. Any other value is reported by the push and no rows are written, so a mistyped parameter value cannot silently remove rows. Setting Array size to 0, or to a parameter that resolves to 0, has the same effect as false for that member.
Include when is different from Enable. Enable creates the row and leaves it switched off, which keeps the variable, its history settings and its address in the project. Include when means the row does not exist. Use Enable to ship a point that is commissioned later, and Include when for equipment an instance does not have.
Include when is part of the definition version, so changing it marks the instances of the type as Outdated. The next Push creates the rows of members an instance now includes and deletes the rows of members it now excludes. Those deletions appear in the push preview like any other delete, together with a warning when another condition or expression still references the variable. History already recorded for a deleted variable stays in the historian, and re-including the member later creates the row again under the same name.
Alarm Members and Event Members
Alarm and event members become real alarms and events per instance. The Condition, Message, and Actions fields accept tokens. Combining the built-in tokens with numeric parameters gives per-instance setpoints from a single template, for example the condition {Device}.{Instance}_Speed > {HighSpeedLimit} with the message {Asset}: pump speed high.
Materialized alarms keep their runtime state across pushes: an update to an alarm member changes the existing alarm in place instead of recreating it, so acknowledgements are preserved.
The Suppression group of an alarm member holds Suppress When, Release Delay (s) and Parent Alarms. All three accept tokens and are pushed like Condition and Message. While the resolved Suppress When expression is true, the materialized alarm is suppressed by design: it is still evaluated and logged, but it is listed under Suppressed Alarms in the Alarm Viewer instead of Active Alarms. Release Delay (s) is the number of seconds a still active alarm stays suppressed after the cause clears before it is annunciated.
The Timing group of an alarm member holds On Delay (s) and Off Delay (s): the number of seconds the Condition must be true before the materialized alarm becomes active, and false before it returns to normal. Both accept a number or a parameter token such as {OnDelay} and are pushed like Condition and Message. An empty field is 0. The Alarms article describes their behaviour.
The Timing group of an alarm member holds On Delay (s) and Off Delay (s): the number of seconds the Condition must be true before the materialized alarm becomes active, and false before it returns to normal. Both accept a number or a parameter token such as {OnDelay} and are pushed like Condition and Message. An empty field is 0. The Alarms article describes their behaviour.
Parent Alarms names the parent alarms by their condition, one condition per line, for example {Device}.{Instance}_PowerFail == 1. The Add parent alarm... list under the field adds the condition of another alarm member of the type. At the push, each line is linked to the alarm with exactly that condition: the alarms of the same instance first, then any other alarm, including the alarms configured on the Alarms page. While one of its parent alarms is active, the materialized alarm is suppressed as described in the Alarms article. When Parent Alarms is empty, the parent alarms of the materialized alarm are set per alarm on the Alarms page and a push leaves them unchanged. When it is filled in, the definition owns them and they change through the definition.
Push refuses an instance where the resolved Suppress When reads a counter the materialized alarm itself is part of, for example the counter of its own severity for its own UDT instance, and an instance where a Parent Alarms line matches no alarm or more than one alarm, names the alarm itself, or closes a loop of parent alarms. The Alarms article describes these counters.
Each row has an expander that opens a detail area holding the Actions of that member. The area lists the action types currently configured, an Edit Actions button carrying the number of actions, and a collapsible Actions (JSON) view of the stored value.
Edit Actions opens the same actions editor used by the Alarms and Events pages, with the same action list and the same fields, so an action is configured the same way everywhere it appears. The editor writes its result back to the member when it is closed. Nothing reaches the instances until the definition is saved and pushed.
The Actions (JSON) view is read-only and shows the value exactly as it is stored. This is the format the Export and Import buttons of the tab use, and it is where the tokens stay visible in full. It is collapsed by default and stays open per row while the editor is open.
| Action | Fields | Example |
|---|---|---|
| None | A configured action that does nothing. | |
| Set | Variable | {Device}.{Instance}_AlarmOut |
| Reset | Variable | {Device}.{Instance}_AlarmOut |
| Toggle | Variable | {Device}.{Instance}_Mode |
| Momentary On | Variable | {Device}.{Instance}_Jog |
| Set Value | Variable, Value | {Device}.{Instance}_Setpoint with value {HighSpeedLimit} |
| Send Email | Email To, Email Body | maintenance@site.local with body {Asset}: {Message} |
| Execute Script | Python Script | PumpFaultReport |
| Transfer Recipe | Recipe From, Recipe To | RecipeA to RecipeB |
| Sample Group | Sample Groups | GroupA |
No rows match the filter.
Action fields accept the same tokens as Condition and Message, for example a Set action on the variable {Device}.{Instance}_AlarmOut. Tokens are resolved when the instance is pushed and the resolved action is validated at that moment, so an action that is incomplete only after resolution, for example a Set Value whose Value token resolves to an empty parameter, is reported in the push preview and stops the plan for that instance.
Actions are validated when they are saved. An unknown action type, a missing required field, a field belonging to a different action type, and a placeholder the object does not provide are each reported with the position of the action in the list. The same validation runs on the Alarms, Events, and Schedules pages, in the Configuration API, and on import, so a file that is accepted in one place is accepted in all of them.
The placeholders available in an action value depend on the object that runs it. An alarm provides {Condition}, {OnTime}, {Message}, {Asset}, and {Severity}. An event provides the same set without {Severity}. A schedule provides {FromTime}, {ToTime}, {Repeat}, and {Asset}.
Instances
The instance list of a definition shows each instance with its devices, asset path, parameter values, synchronized version, and status. An instance that is created on more than one device lists them all, and its detail panel shows the number of devices next to the name. Create an instance with the Add instance button. The dialog contains the Name and Asset fields on the left and one input per parameter on the right. An empty parameter input inherits the definition default, shown as the placeholder.
There is no separate Device field and no device list. A device is an ordinary parameter, so a Device parameter is typed in the parameter column like every other one, and an instance with several Device parameters is created across several devices. There is no device type field either: the device name determines the protocol, because device names are unique across all protocols. The Asset field takes an Asset Manager node path such as Site/Line A/Pumps; missing nodes are created automatically. Creating the instance stages it: the instance is saved with all its values and shows the Not materialized status, and its variables, alarms, and events are created by its first push. The save already reports the findings the next push would report, for example an unknown device name, so a mistake is visible immediately while the entered values are kept.
A definition that declares exactly one Device parameter can create a fleet in one action. Separate several device names with a comma in that parameter, for example PLC1, PLC2, PLC3. One instance is created per device, and the instance name uses {Device} as the per-device pattern, for example {Device}_Meter. Device names never contain a comma, so the separator is unambiguous. This creates a full set of identical equipment, such as twenty power meters, at once.
Editing an instance changes only that instance and stages the change: renaming it, moving it to another asset node, or changing parameter values marks the instance Outdated, and its materialized rows are rewritten by the next push. The definition of an existing instance stays fixed.
Changing a Device parameter and pushing moves the variables of the members that use it. When the new device uses the same protocol, the variable is moved as it is and keeps its alarm state. When the new device uses another protocol, the variable is created again from the protocol group that matches the new device; its trend history stays available because the Historian records under the variable name.
Deleting an instance deletes its materialized variables, alarms, and events. A device that still carries variables materialized by a UDT instance is kept until the instance is deleted or detached, or its members are pointed at another device.
To create several instances at once, set Count in the Add Instance dialog to a value above 1. The Name must then contain {N}, which is replaced by the running number: Oven{N} with Count 4 and Start at 1 creates Oven1 to Oven4. Zero-pad formats the number with leading zeros. Count combines with the multi-device selection, so {Device}_Oven{N} across two selected devices with Count 4 creates eight instances.
Use the Copy action on an instance to create a similar one: the Add Instance dialog opens prefilled with the definition, asset and the explicitly set parameter values of the source instance, including its devices. Enter a new Name and adjust the parameters before saving. Inherited parameter values stay inherited on the copy.
The Name of an instance is the name it has within its asset node, for example Pump1. The full name is composed of the asset path and this name, separated by dots, as in WTP.Distribution.Pumps2.Pump1, and is shown under the Name field while typing.
Only the full name has to be unique in the project, so the same name can be used on several asset nodes. A pump called Pump1 can exist on every line, each one distinguished by its own path.
Moving an instance to another asset node, or renaming a node above it, updates the full name immediately. The symbols placed for that instance follow on its next push, and the instance shows the Outdated status until then. Variables, alarms and events keep their names, because {Instance} resolves to the name within the node, which does not change.
Push
Every instance shows one of three statuses. Not materialized means the instance is staged only and no rows exist yet; Outdated means rows exist but a push would change them, because the definition was saved with changes, instance edits were staged, or the instance moved with the asset tree; Synced means a push would change nothing. Deleting an instance is the one immediate operation: it removes the instance and its materialized rows without a push. The Push button computes a plan per instance and shows it in the Push preview dialog before anything is written. Every outdated instance is listed in the preview with a check box and is selected by default, and the Push button shows how many of the listed instances are selected. Clear the check box of an instance to hold it back: the selected instances are pushed, and the cleared one keeps its Outdated status and its current rows until a later push. A push is applied per instance, so any selection is valid, and an instance does not have to be detached to be left out of a push.
The preview lists every change as an addition, update, or deletion of a variable, alarm, or event, with the changed fields per row. A variable that moves to another device of the same protocol is listed as an update carrying its device change. A variable that moves to a device of another protocol is listed as a deletion and an addition, with a note that the variable is created again and that its trend history follows the variable name. Deletions carry warnings when the variable is still referenced by alarms, events, Python scripts, or schedules, and when history recording stops.
A reason is recorded in the Audit Trail together with the push. It is required when a selected instance is already materialized; when every selected instance is new (Not materialized), the reason is optional because the push is the initial create. A plan with blocking errors, for example a name collision with an existing variable or a row managed by another instance, is skipped as a whole. A push is never applied partially.
Pushing an instance also updates the symbols bound to it. The mapped replacer values on those symbols are rewritten and each symbol is resynchronized with the current markup of its master, so an unfinished change to a master reaches every page carrying one of its instances on the next push. The graphics side can also be refreshed on its own, without a push, from the Instance Replacers dialog of a symbol.
Detach and Adopt
Detach removes the UDT link from all materialized rows of an instance and removes the instance itself. The rows keep their current values and become ordinary rows that can be edited in place again. Detach is one way; the reverse operation is Adopt.
Adopt materializes an instance over existing rows: when the Adopt existing rows option is checked in the Push preview dialog, rows whose names match the names the push would create are taken over instead of blocking the push. Their managed fields are overwritten with the definition values and they become managed rows. Toggling the option refreshes the preview, so the take-overs are visible before anything is written. Adopt is the recommended way to convert devices that previously used the device Template field into UDT instances.
Managed Rows
Variables, alarms, and events that belong to a UDT instance are shown with a highlighted background on their configuration pages and cannot be edited or deleted there. The definition owns every setting of a materialized row, so a write that changes one is refused with a message naming the owning instance and the fields concerned. Creating or deleting a managed row is refused outright.
An instance can take an individual setting back with Override. In the Asset Manager, open an instance, expand a variable and click the pen next to a field: the field becomes editable on that instance, a Push stops maintaining it, and the pen turns to the accent colour. Click it again to revert, which restores the definition's value immediately. The same field then becomes editable in the variable dialog and through the Configuration API. Name and Condition can never be overridden, because they are how a Push finds the row again. Alarms and events work the same way.
A definition can declare which settings start out overridden, using the pen next to each field in the UDT definition's variable settings. Modbus Address is set that way by default, because each variable needs its own address. Marking a field there affects variables created afterwards; it never changes overrides that already exist.
A variable member has a Recipe Targets setting of its own, owned by the definition like the rest of the member. It accepts the same replacers as the other member settings, so a member set to {Instance} places every instance's variable in its own recipe target, and {Device} is kept as written so the target follows a device rename. An instance that needs a different target overrides the field.
The variable settings also cover the Modbus Address the built-in Modbus server exposes the variable on, the MQTT Publisher, the Kafka Device, Kafka Remap Device and Kafka Remap Variable, the OPC UA queue settings and the Modbus scan block flag, so a definition can set them once for every instance instead of variable by variable. These gateway settings are owned by the definition like every other member setting: a Push rewrites them on the materialized variables, and an instance that needs its own value overrides the field. A device that is the target of UDT instances cannot be deleted until those instances are deleted or detached.
The device Template field is replaced by the UDT Manager and is no longer shown in the device dialogs. Existing template values keep working; converting a template device group to a UDT definition with adopted instances is the recommended path forward.
Symbol Binding
The Symbol tab links a definition to the graphics that represent it. A definition can bind more than one group symbol master, for example an overview symbol and a detail symbol used on different pages. Add group symbol adds a block, and each block binds one master.
Group symbol is a list of the group symbol masters present in the project. Selecting a master fills the table below it with one row per replacer that this master declares, showing the replacer label and the master default. Token expression is what the UDT writes into that replacer, for example {Device}.{Instance}_Speed.
A replacer with an empty Token expression stays owned by the designer and is never written by a push. This is how a mapping is removed. Changing the master of a block clears that block's mapping, because the replacers of the previous master do not exist on the new one.
Find master next to the picker opens the page that holds the master and marks the master with a blue frame for 10 seconds. This closes the Asset Manager, so the definition is asked to be saved or discarded first.
A master that is stored on the definition but is no longer present in the project is kept in the list and flagged. Its mapping is left untouched so that a binding is not lost when a symbol is temporarily unavailable, for example while a page is being imported.
Place on page in the instance detail creates a symbol for that instance. When the definition binds more than one master, the dialog asks which master to place. One action places one symbol, so a second master is placed by repeating the action.
A symbol placed for an instance carries the instance name and prefills the mapped replacers with the resolved token values. On every push, the mapped replacer values on those symbols are updated, and the symbol is also resynchronized with the current markup of its master. Position, size, and all replacers outside the mapping stay owned by the designer.
An existing group symbol instance can also be linked to a UDT instance from the Design area. Select the symbol and use the UDT Instance button in its group settings. The list offers only the instances whose definition binds this symbol's master, so the symbol and the type always agree. The linked instance name is shown next to the button. The icon beside that name opens the Asset Manager on the linked UDT instance, with the instance selected in the tree and its details shown.
Linking records the reference and changes nothing on the page. The mapped replacer values are applied by the next push of that instance. When the mapping expects replacers the symbol does not carry yet, the count is reported at link time and the push adds them from the master.
Unlink from UDT Instance removes the reference. The symbol keeps its current replacer values, its position, and its own master, and continues to work as an ordinary group symbol instance. Pushes of the UDT instance no longer reach it.
Deleting a UDT instance leaves its symbols on the page. They keep their last values and show NOT FOUND for the variables that no longer exist. Repair them with the ordinary design tools, by linking them to another instance or by changing their master. The same applies when a master is removed from a definition: the symbols placed from it stop being updated and own all their replacers again.
Trend Analyst Configuration
The definition editor has a Trend Analyst Configuration field next to Name, Description and Base type. It holds a Trend Analyst configuration in JSON, the same document the Config button of a Trend Analyst viewer and the Trend Analyst button action use, and its Config button opens the same editor. Pen variables and names are written with the tokens of the definition, for example {Device}.{Instance}_Speed, exactly as a variable member name is written.
The configuration is used by the Navigator object: an instance row of a definition with a configuration shows a trend icon, and clicking it resolves the tokens for that instance and opens the Trend Analyst, or the Trend Page of the Navigator, with the resulting pens. The field is definition metadata: changing it does not change the version of the definition, so instances do not become Outdated and no Push is needed. It is exported and imported with the definition, through the project export, the Git repository and the type export.
Instance Replacers and the Push to Instances Dialogs
The replacers of a bound symbol are owned in three parts. The markup belongs to the group symbol master, the mapped replacer values belong to the UDT instance, and everything else, including position, size, and the unmapped replacers, belongs to the designer.
In the Instance Replacers dialog of a bound symbol, a UDT column marks the replacers owned by the UDT instance and their Replacement input is disabled, because a value typed there would be replaced by the next push. Update from UDT fills those rows with the values the instance currently resolves to. The dialog shows the result and applies nothing until OK is pressed, so the change can be checked first. Open UDT instance opens the Asset Manager on the bound instance.
The Update button of a symbol keeps its original meaning for bound and unbound symbols alike: it pulls the markup from the master. It does not refresh the UDT values. Refreshing those is the separate Update from UDT action described above.
Push to Instances lists the instances of a master as rows with one column per replacer. The Instance, Page, and Status columns stay in view while the replacer columns scroll. When at least one instance in the list is bound to a UDT, a UDT column shows the bound instance name. Push to Instances Matrix shows the same data one replacer at a time, with the replacers listed on the left, and the two views share their selection, search, and edits.
Cells owned by a UDT instance are disabled in both views. Ownership is per cell, not per column, because two instances of the same master can belong to different definitions.
The refresh button in a row's UDT cell fills every UDT-owned replacer on that one symbol. Fill all from UDT in the UDT column menu does the same for all selected and visible rows, and Fill from UDT in a replacer column menu fills only that replacer. These actions only appear where an instance actually owns a value through a UDT.
A symbol whose binding no longer resolves, because the definition no longer binds this master, is marked in the UDT column. UDT pushes skip such a symbol. Relink it to a suitable instance, or add the master to the definition.
Exporting and Importing Types
UDT definitions can be moved between projects as a portable type file. In the UDT Definitions list, Export writes every type to a single JSON file, and Import reads such a file back.
The file records the schema version of the project it came from. On import, a file from a newer version than the running application is refused, and a file from an older version is upgraded to the current shape. A type whose name already exists is skipped and listed; you are then asked whether to replace the existing types with the imported versions. Replacing a type reuses its members so that instances stay linked, and those instances show the Outdated status until the next push.
Include symbols, next to the Export button, also writes the group symbol masters the exported types are bound to. The masters they depend on travel with them: the symbols nested inside them, and the faceplate masters their popup actions open. The Color Library entries the symbol markup uses by name are written to the file as well, with their value in every theme. Leave the option clear to write the types only, which is the smaller file and the right one when the receiving project already holds the symbols.
When such a file is imported, the masters are written first, so the types find their symbols immediately. Only the masters the imported types need are written. Each master is placed on the page it came from, and that page is created when it does not exist. A master whose name is already present in the project is skipped, unless you confirm replacing existing content: a replaced master leaves the instances already placed in this project on their previous content until you push it. Color Library entries are added only when the name is free; a name the project already defines keeps its own value, and the import reports which ones were kept.
When the file carries no symbols and an imported type names a group symbol master that is not present in the project, the import reports a warning and still imports the type.
Base type
A UDT definition can derive from another definition by selecting it in the Base type field of the definition editor. The base type's parameters, variable members, alarm members, event members, scripts and slots become part of the deriving type. The deriving type can add its own members and parameters on top of them.
A parameter that is declared in both the base type and the deriving type must keep the same Type; the deriving type then changes only its Default value. Members of the base type cannot be redefined in the deriving type. Members that come from the base type are listed read-only under Inherited and contained members on the Variables, Alarms, Events and Scripts tabs, with an Edit button that opens the definition that owns them.
Editing the base type changes the effective content of every deriving type, so the instances of the deriving types show Outdated and receive the change with the normal Push. Renaming a base type does not affect its deriving types or their instances. A definition that is used as a base type cannot be deleted until the deriving definitions no longer reference it.
Slots
The Slots tab composes a definition out of other definitions. Each slot has a Name, a Contained definition and an optional IncludeWhen condition. Every instance of the outer definition materializes the contained definition's members once per slot. A skid definition with the slots MotorA and MotorB of a motor definition therefore gives every skid instance two complete sets of motor variables and alarms.
The Parameter mapping table under each slot lists the parameters the contained definition declares. Each expression is written in the outer definition's namespace, for example {= {SkidBase} + 100 } for a base address or {Plc} for a Device parameter, so each slot can point at its own addresses and its own device. A parameter without an expression uses the contained definition's own default.
Inside the members of a contained definition, {Slot} resolves to the slot name and {SlotPath} to the full dotted path. When a definition fills two or more slots of the same contained definition, the contained definition's member names must contain {Slot} or {SlotPath}; otherwise the slots would materialize identical names and the save is rejected.
A slot's IncludeWhen condition is evaluated per instance in the outer namespace; when it resolves to false the whole slot materializes nothing for that instance. A definition that is used in a slot cannot be deleted until the containing definitions no longer reference it. Exporting a definition automatically includes its base type and contained definitions in the type file.
Scripts
The Scripts tab gives a definition its own logic: Python scripts that every instance of the type runs. The script body is shared by all instances and reads its per-instance values with GetParam, for example GetParam('Instance'), GetParam('Asset'), GetParam('Slot'), GetParam('SlotPath') and every definition parameter by name. Combined with the standard script functions this addresses the instance's own variables: speed = GetFloatValue(GetParam('Instance') + '_Speed').
Each script has three triggers, and all of them accept tokens so every instance can run at its own cadence:
Interval (ms) runs the script periodically.
RunWhen runs the script once each time the condition changes from false to true. Nothing is written to the event history; use an event member when the occurrence itself should be recorded.
Run at startup runs the script once when the application starts and when a redundant server becomes the active server.
An IncludeWhen condition excludes the script for instances that resolve it to false, like any other member.
Network & File Access allows the script to open network connections, serial ports and files outside the project, as on the Python Scripts page. Each instance then runs the script in the access worker, and the script can only write device variables and External Source variables. It is a checkbox of the script, applied to every instance by the push.
Script changes follow the same delivery flow as every other member: saving the definition marks the instances Outdated and the Push delivers the new logic, so an instance keeps running the logic it was last pushed with until it is pushed again. Each instance's execution state is visible on the variables System.PythonStatus_<instance>.<script> and System.PythonMsg_<instance>.<script>, which can be used in alarm conditions. The Python Scripts page is not involved; type-owned scripts are managed entirely from the definition.