TwinCAT and Unity – Part #4

Welcome back! Once again we are returning to the world of simulation. In this part we will deliver a fully working pneumatic actuator – a humble double acting cylinder, widely used in modern automation systems. Our goal is to create a fully working cylinder with two end positions (work and home) reflected by the state of the sensors.

Animation in Unity

To simulate the behavior of the cylinder we will utilize Unity’s animation components: Animator, Animation Controller and Animation. To simplify the concept of animations in Unit we have to remember following relations:

Animator – component added to a model/mesh which properties are going to be animated

Animation Controller – a state machine which coordinates which animations should be started in which order, under what conditions

Animation – description of how specific property/properties of animated object are changing in time and what (if any) triggers are being set and when

I have created a 3D model of 100mm stroke cylinder, which is available to download from my GitHub page.

Cylinder model and its structure

The model comprises of 4 meshes. “Body” is the static frame of the actuator, “Piston” is the movable part of the model and two sensors which at the moment are just props. We will simulate sensors purely in the code, without interacting with those two meshes.

To follow the Unity’s animation logic we have to add Animator component to the Piston mesh.

Next we have to create Animation Controller – I have created a new folder in Assets directory in which all animation components will reside. I called it DoubleActing_Cylinder to align the names with the model itself. I have also created two animations in the same folder called “DAC_Extend” and “DAC_Retract”. Empty Animation controller looks like this.

When we right click in the Animation controller we will be able to add a new empty state. When we click on the state we will see following tab in the properties

New empty state properties

Now we have to select which Motion (Animation) we want to use in this state. When the state machine will be in this state selected animation will be played (once or more if configured).

Creating animation

The next step is to create the animation itself. Let’s go to the DAC_Extend animation. When double clicked a new window will open with “Add property” button grayed out. To enable the property adding we have to select the mesh we wish to animate. In our case it will be “Piston”. After clicking on “Add property” we can select what property is going to be animated – again, in our case this will be “transform > position”.

Without going into much details, how to create animation in Unity we want to achieve the following – after 500ms the piston will move upwards (along the Y axis) to position 0.31327 from 0.21327. We can modify how this movement occurs (the movement dynamic) by editing the curves.

I have also added an event in the last frame. We will use this later to set/reset sensor state.

We have to create the animation and state for the DAC_Retract as well. It is worth to note the numerical values of the position to make the movement more accurate and life-like. When creating animation for retract movement make sure that the correct animation is selected in the Animation screen. sometimes it is possible to inadvertently overwrite already existing animation.

Complete animation controller should look like this

Complete animation controller with two BOOL parameters

If we start the project now and change the state of the Extended and Retracted parameters we will see that the piston position is changing and the state machine is moving between the states.

Actuator code

When everything is ready, we can start writing the code for the new class – Sim_PneumaticAct.

public class Sim_PneumaticAct : SimulationObject
{
    [SerializeField]
    // Inputs - operate actuator: extend or retract
    public string extendVarName;
    public string retractVarName;
    // Outputs - confirm actuator position extended or retracted
    public string extendedVarName;
    public string retractedVarName;

    private Animator animator;       
 
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        if (TwinCatHandler.ReadBool(pouName, extendVarName))
        {
            if (animator != null)
            {
                animator.SetTrigger("Extend");
                TwinCatHandler.WriteBool(pouName, retractedVarName, false);
            }
        }
        else
        {
            animator.ResetTrigger("Extend");
        }

        if (TwinCatHandler.ReadBool(pouName, retractVarName))
        {
            if (animator != null)
            {
                animator.SetTrigger("Retract");
                TwinCatHandler.WriteBool(pouName, extendedVarName, false);
            }
        }
        else
        {
            animator.ResetTrigger("Retract");
        }
    }

    void ExtendAnimationFinished()
    {
        TwinCatHandler.WriteBool(pouName, extendedVarName, true);
    }

    void RetractAnimationFinished()
    {
        TwinCatHandler.WriteBool(pouName, retractedVarName, true);
    }
}

As always we are using our base class – SimulationObject which will be handled by DI to provide required dependencies. Only new thing in the code is getting Animator object, assigning it to local variable and using it to trigger change of state of “Extend” / “Retract” properties.

There are two additional methods – ExtendAnimationFinished and RetractAnimationFinished. We have to assign those two methods to the events in animations that we have created previously. Thanks to that, once the animation finishes, we will write the state of the end position sensor back to the TwinCAT handler – simulating the sensor.

Before selecting those methods we have to assign the Sim_PneumaticAct to our Piston mesh – this is very important, the code in that class relies on the structure of the objects. For some reason if the Function selection is not active it is helpful to click the “Piston” object to make sure Unity GUI knows what we are referring to.

At this point the last thing to do in Unity is to assign TwinCAT variable names that we are going to use in the PLC project.

As we are using SimulationObject PouName and Var Name are there by the default

We can ignore VarName in this example and populate only POU Name and remaining variables. POU name once again will be DataExchange.

TwinCAT Project

The PLC side of the actuator is a very simple Function Block which can be used in real world application. To interface with the simulation we are still using our DataExchange GVL. I have added 4 new signals for the Double Acting Cylinder.

DAC_Sens_Work	: BOOL;
DAC_Sens_Home	: BOOL;
	
DAC_Coil_Work	: BOOL;
DAC_Coil_Home	: BOOL;

The code for the actuator.

FUNCTION_BLOCK FB_Actuator
VAR_INPUT	
	Sensor_Work		: BOOL;
	Sensor_Home		: BOOL;	
	GoToPos_Work	: BOOL;
	GoToPos_Home	: BOOL;	
END_VAR
VAR_OUTPUT	
	ToPos_Work		: BOOL;
	ToPos_Home		: BOOL;	
	InPos_Work		: BOOL;
	InPos_Home		: BOOL;		
END_VAR
VAR
	Moving			: BOOL;
END_VAR
//--------------------------CODE---------------------------------
// Reset Outputs
ToPos_Work	:= FALSE;
ToPos_Home	:= FALSE;

// Movement initiated
IF (Sensor_Home AND GoToPos_Work) OR
	(Sensor_Work AND GoToPos_Home) OR 
	(NOT Sensor_Home AND NOT Sensor_Work AND (GoToPos_Work OR GoToPos_Home))THEN
	Moving := TRUE;
END_IF

// Position reached
InPos_Home := Sensor_Home AND NOT Sensor_Work;
InPos_Work := Sensor_Work AND NOT Sensor_Home;

IF (InPos_Home AND GoToPos_Home) THEN
	Moving := FALSE;
END_IF

IF (InPos_Work AND GoToPos_Work) THEN
	Moving := FALSE;
END_IF

// Trigger outputs when valid move initiated
IF Moving AND GoToPos_Work THEN
	ToPos_Work := TRUE;
END_IF

IF Moving AND GoToPos_Home THEN
	ToPos_Home := TRUE;
END_IF

Function block call in the MAIN program.

//------------------------VARIABLES-------------------------------
fbActuator		: FB_Actuator;
GoToWork		: BOOL;
GoToHome		: BOOL;
InWork			: BOOL;
InHome			: BOOL;
//------------------------CODE------------------------------------

fbActuator(
	Sensor_Work := DataExchange.DAC_Sens_Work,
	Sensor_Home := DataExchange.DAC_Sens_Home,
	GoToPos_Work := GoToWork,
	GoToPos_Home := GoToHome,
	ToPos_Work => DataExchange.DAC_Coil_Work,
	ToPos_Home => DataExchange.DAC_Coil_Home,
	InPos_Work => InWork,
	InPos_Home => InHome);

To initiate the actuator movement we will set the GoToWork bit. To move it to the opposite direction use GoToHome. Note on nomenclature, by home position we mean fully retracted actuator. Forcing variables in TwinCAT will give us full control over the actuator in Unity.

Putting everything together should give us a fully working actuator.

4 thoughts to “TwinCAT and Unity – Part #4”

  1. Thank so much for this absolut genial tutorial. For those one i have been searched a long time. And yours is just what i need. Thanks for divide your experience with others.

    1. Hi Jakob! Thanks for the endorsement! I’m a big fan of your blog as well, great source of inspiration. I’ll link back to yours as well.

Leave a Reply

Your email address will not be published. Required fields are marked *