10+ Years in Automation, PLC and HMIs. Working with most major Automation and Instrumentation vendors
What Is A Variant?
Variants are extremely powerful and allow the passing of almost any type of data into a Function or Function Block.
A Variant is exactly 0 bytes in length (which doesn't make sense I know, but trust me, it doesn't take up any length in the interface), which means variants themselves cannot hold any actual data. They are used as pointers to other data of a known structure or type. The data type of the variant must be available to the function block in which the variant is being used, this will be more clear as we work through the example.
When To Use Variants?
Variants offer no value unless you are looking to create functions that behave differently depending on data passed to it.
Consider this example:
You have an application that consists of 20 valves, these valves are all the same hardware type and have all of the same signals. They all share the same parameter structures except for a few parameters that denote how the valve behaves.
In the above image, the "Data" input is a Variant (Highlighted in Red). It appears like any other interface pin. Variants can only be declared as Inputs or InOuts. They cannot be declared as outputs, they also cannot be declared in the static data, but can be used in temporary data.
In this case the structure "HMI_Data".MV101.NAW is being passed to the Variant input. For this function block the "Data" InOut is the only "non-standard" part of the function. Everything else on the interface is standard to the valve control, no matter what is specified at the Data interface.
Take a look at the below image, you can see that the interface is exactly the same, because its the same function block, but the data being passed is different on the "Data" Variant InOut.
On face value, looking at the two blocks, nothing appears to be any different. But inside the block, the function is reacting to the Variant "Data" value being different.
So how is this done?
Checking Variant Type
This can only be done in SCL (Structured Text) using the "TypeOf" instruction.
The TypeOf instruction allows the Function Block to check the data type that is being passed to the Variant. This can be used to check against a type that is declared in the function block (or globally) to determine what is available in the Variant.
See the below example:
Using an IF statement and the TypeOf instruction, the "Data" Variant is checked for its type. If the Variant type matches the type tied to the variable in the IF statement, a "Move_Blk_Variant" instruction is performed. This moves the Variant data into the local defined structure.
Now the data is in a local structure, it's elements are known and can be used as normal. You'll notice that a "Type" variable is also set, this then allows the logic to check which Data Type is in use and act accordingly:
The above demonstrates this. If the structure passed to the Data Variant is "UDT_PID" then the Ladder rungs with "Type = 0" execute. If "UDT_NAW" is passed, then "Type = 1" execute. This allows different behaviour from the same function block for similar types of hardware, in this case, valves.
At the end of the function block, there needs to be a method of writing data back through the Variant to the structure passed to "Data":
The above simply reverses the earlier process, using the Type variable to determine which data type to pass back to "Data".
MV_PID and MV_NAW are declared as Temps in the function block as their respective UDT types (UDT_PID and UDT_NAW)
This approach is highly scalable. For example, if another mode was required for these types of valves that required a different dataset, a new UDT can be created and the FB updated to check the Variant data for that type. From then, only logic needs to be updated.
This approach allows interfaces to be updated, changed or modified with relative ease, with the changes propagating through to all instances.
The downsides of this approach is that it can (not always) make debugging harder and it also uses more memory as logic that wont be used is still being loaded in each instance.
The upsides though are very fast development and much tighter control of libraries as your block count can be majorly reduced.
Variants are worth looking at in any case, they can really save some time and also save repeated code in different blocks.