TwinCAT and Unity – Part #2

Welcome to the second part of TwinCAT and Unity series. In this post we will start from refactoring the code from the first part, to make it more robust and make our classes loosely bound. Later we will extend our interface class to write bool values and implement our first sensor.

Tight coupling issue

When we look at our FB_SquareWave class we will see that it has a public property of TwinCAT_Handler type. We need to remember that every time we create a new object we have to populate this property by hand. Not a very painful task, but what if we would like to rewrite the TwinCAT_Handler class or replace it with something else? It also would be difficult to test this class in isolation. There is no mechanism, at the moment, that would allow us to, for instance, mock the responses from the PLC to test the Unity class behaviour. We depend too much on implementation of that class.

The solution is to use dependency injection (DI) design pattern. For those who are unfamiliar with the concept, in simple terms dependency injection is a technique where dependency is passed to the dependent object and it becomes part of its state. In our case FB_SquareWave requires TwinCatHandler to interact with the PLC. We will pass the TwinCat handler to it, allowing it to utilize its methods. We will do it using code and in a way that will allow us to never worry about this again. 

Creating interfaces

Most common implementation of DI is using class constructors. This won’t work in Unity. As all game objects (our simulation elements) inherit MonoBehaviour base class, it’s Unity that controls their constructors. Another popular implementation of DI utilizes properties – we will use this approach. We’ll create two interfaces IRequireTwinCatHandler and ITwinCatHandler. First interface will indicate all objects (future and present) that will require TwinCatHandler parameter. Second interface will provide description of what methods are available and should be implemented by real or mock TwinCat handlers.

We start by creating TwinCATHandler folder in our Classes folder. In that folder we’ll create two new interface files. Their code below.

public interface IRequireTwinCatHandler
{
   ITwinCatHandler TwinCatHandler { get; set; }
}

public interface ITwinCatHandler
{
   void InitializeConnection();
   bool ReadBool(string pou, string variableName);
   int ReadInt(string pou, string variableName);
   bool WriteInt(string pou, string variableName, int value);
}

We can see that ITwinCatHandler interface contains all methods we created in part 1 of this series. New item is InitializeConnection() – this method contains what was the content of Awake() method. As a next step of refactoring we move TwinCAT_Handler to newly created folder and change the content of the file to the following:

public class TwinCAT_Handler : ITwinCatHandler
{
    private TcAdsClient _tcClient = null;

    public void InitializeConnection()
    {
        _tcClient = new TcAdsClient();
        _tcClient.Connect([ADS ID], 851);
        if (_tcClient.IsConnected)
        {
            Debug.Log("Twin CAT ADS port connected");
        }
        else
        {
            Debug.LogError("ADS Connection failed");
        }
    }

    public bool ReadBool(string pou, string variableName)
    {
        // no change here
    }

    public int ReadInt(string pou, string variableName)
    {
	// no change here
    }

    public bool WriteInt(string pou, string variableName, int value)
    {
	// no change here
    }
}

The main change in the code is getting rid of MonoBehaviour inheritance and all methods that it provides (Awake, OnUpdate and so forth). The only thing we require is indication that this class implements ITwinCatHandler.

Time to update our FB_SquareWave class. We need to indicate that this class will implement IRequireTwinCatHandler and its functionalities.

public class FB_SquareWave : MonoBehaviour, IRequireTwinCatHandler
{

    [SerializeField]
    //public TwinCAT_Handler _tcHandler;
    public string sPouName;
    public string sStateName;
    public string sPulseLengthName;
    public int iPulseLength;

    public ITwinCatHandler TwinCatHandler { get; set; }
...

We are removing _tcHandler property and introducing TwinCatHandler property. We need to rename all _tcHandler to TwinCatHandler and remember to add appropriate namespaces (using Assets.Classes.TwinCATHandler;).

We are now ready to create entity that will maintain dependency injection. Let’s create new class called SimulationController – this class should be standard Unity class inheriting from MonoBehaviour.

public class SimulationController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        InitializeTwinCATInterfaces();
    }

    private void InitializeTwinCATInterfaces()
    {
        // initialize new TwinCAT interface handler and inject it into the 
        // required objects
        ITwinCatHandler twinCatHandler = new TwinCAT_Handler();
        // initialize connection
        twinCatHandler.InitializeConnection();

        // Find all elements that require TwinCat handler
        var elements = FindObjectsOfType<MonoBehaviour>().OfType<IRequireTwinCatHandler>();
        foreach(var element in elements)
        {
            element.TwinCatHandler = twinCatHandler;
        }
    }
}

InitializeTwinCATInterfaces() method does three things – creates a new instance of TwinCAT_Handler class, initializes connection to the PLC and finds all MonoBehaviour objects which implements IRequireTwinCatHandler interface and passes TwinCat_Handler object to them.

Last step in our refactoring is to delete TC_Handler game object from the scene and add new empty game object called SimulationController and assign SimulationController class to it. The project should be now ready to run. If everything goes well, the behavior of the project should remain unchanged.

Writing boolean values

Before we start introducing sensors we need to create method for writing boolean value to the PLC. Following the documentation guidance our writing method will utilize WriteAny() function from ADS library. Because we are using interface now, we have to update the implementation declaration as well as the implementation itself. In the interface, we update

bool WriteBool(string pou, string variableName, bool value);

In the implementation

public bool WriteBool(string pou, string variableName, bool value)
{
   try
   {
      var hVar = _tcClient.CreateVariableHandle(pou + "." + variableName);
      _tcClient.WriteAny(hVar, value);
      _tcClient.DeleteVariableHandle(hVar);
      return true;
   }
   catch (AdsErrorException exp)
   {
      Debug.LogError("TC Write Error - Bool" + exp.Message);
   }
   return false;
}

Using the new feature

To test our new feature we will need some PLC side code. I have written a small toggle switch implementation which changes the output status in reaction to rising edge on input. Let’s create a new POU in common folder of our project.

//--------------------------------- DECLARATION -------------------------
FUNCTION_BLOCK FB_ToggleSwitch
VAR_INPUT
	switch : BOOL;
END_VAR
VAR_OUTPUT
	status : BOOL;
END_VAR
VAR
	pEdge : BOOL;
END_VAR

//------------------------------------ CODE -----------------------------

IF switch AND NOT pEdge THEN
	pEdge := TRUE;
	IF NOT status THEN
		status := TRUE;
	ELSE
		status := FALSE;
	END_IF
END_IF

pEdge := switch;

I have also added two new variables to our DataExchange GVL.

button1 : BOOL;
status1 : BOOL;

And a call in Main program, with appropriate variables declaration.

fbToggle(
	switch := DataExchange.button1,
	status => DataExchange.status1);

Going back to Unity, we have to prepare a class that will utilize our newly created function block.

public class FB_Toggle : MonoBehaviour, IRequireTwinCatHandler
{
    public ITwinCatHandler TwinCatHandler { get; set; }

    [SerializeField]
    public string sPouName;
    public string sButton;
    public string sState;

    private bool bState;
    private bool bLast;

    // Update is called once per frame
    void Update()
    {
        bState = TwinCatHandler.ReadBool(sPouName, sState);
        toggleColor(bState);
    }

    void OnMouseDown()
    {
        updateVariable(true);
    }

    void OnMouseUp()
    {
        updateVariable(false);
    }

    void updateVariable(bool variable)
    {
        if (variable != bLast)
        {
            TwinCatHandler.WriteBool(sPouName, sButton, variable);
            bLast = variable;
            Debug.Log("Writing button state:" + variable.ToString());
        }
    }

    private void toggleColor(bool state)
    {
        var objectRendered = gameObject.GetComponent<Renderer>();
        if (state)
        {
            if (objectRendered != null)
            {
                objectRendered.material.color = new Color(255, 0, 0);
            }
        }
        else
        {
            if (objectRendered != null)
            {
                objectRendered.material.color = new Color(0, 255, 0);
            }
        }
    }
}

In this class we are using two new MonoBehaviour methods – OnMouseDown and OnMouseUp. They are being called when the game object is clicked. This is a very simple model of mono stable or takt switch. We send TRUE when the switch is pressed and FALSE when it is released. To visualize PLC response, we use toggleColor function.

The xext step is creating new game object in the scene and adding our script to it.

When we start our PLC and Unity project we should see that the cube changes color from green to red and back, when clicked. Once again we prevent flooding our communication chanel by remembering the last written state. Because of this we can see in the console that each click produces only one TRUE and one FALSE signal.

Creating a sensor

Finally we are ready to create our first sensor. To ease the process a bit I have created a small 3D model in Blender to visualize our work. The model is fairly simple and can be downloaded from here.

Let’s assume that this looks like a simple diffuse sensor. Nothing too fancy. On the top there is a small “indicator” that we’ll use to feedback the sensor state in Unity. The sensor will work as NC (normally closed) and the light will go green when there is an object in front of the sensor, or red if the coast is clear. Sensor can be imported to Unity assets – the sensor model fits the real dimensions of the sensor (90x60x30 mm) so it will look very small when imported. Model can be imported without any adjustments.

In our Unity project I have removed most of the stuff from previous tests and left only Simulation controller and added new Cube.

Now, we have to create a new class that will implement behavior of our sensor. To detect objects in front of the sensor we will use Unity’s ray casting mechanism – simple and effective. Our sensor will cast a ray forward and when the ray hits something, we will write one of the PLC variables. In the same time we will change the color of the Indicator component of our sensor. Code below is called in Update method. The class implements IRequireTwinCatHandler and has a set of typical public and private variables we’ve been using so far.

private void IR_Ray()
{
    var direction = this.transform.TransformDirection(Vector3.forward);
    Transform trans = this.transform;
    Transform child = trans.Find("Indicator");
    //leave the function if parent transform does not contain "Indicator" child
    if (child == null) return;
    var objectRendered = child.GetComponent<Renderer>();
    RaycastHit hit;
    Debug.DrawRay(this.transform.position, direction);
    if (Physics.Raycast(this.transform.position, direction, out hit, 0.9f))
    {
        objectRendered.material.color = new Color(0, 255, 0);
        if (!bLastState)
        {
           TwinCatHandler.WriteBool(sPouName, sStateName, true);
           bLastState = true;
        }
    }
    else
    {
        objectRendered.material.color = new Color(255, 0, 0);
        if (bLastState)
        {
            TwinCatHandler.WriteBool(sPouName, sStateName, false);
            bLastState = false;
        }
    }
}

To help visualize ray casting Debug.DrawRay method is being used. The complete scene looks like this:

And when the ray hits the box:

On PLC side we have to add a variable to our DataExchange structure, for instance “sensor1”. If everything goes well we’ll be able too see state change in the PLC, when the box moves in front of the sensor.

Sensor implemented! In the next part we’ll continue creating new simulation primitives and let the PLC handle some real tasks. There is still a lot to do before we can start simulating for real.

Leave a Reply

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