TwinCAT HMI User Control, View, Region and Content

User Control

In TwinCAT HMI, user control is a control that is created by the user.  As an example, a button that we find in the toolbox is a user control but it is system control provided by the TwinCAT HMI framework. User control is made by using multiple system controls.  We can make a composite control (by reusing system control as many as needed).  Then the composite control will be reused as many times as needed. This is like a class in software engineering where we can create an instance of it as many times as needed. Each instance can have different properties for each class member. A User Control is an object type of its own. A user control should have at least one system control. By using user control we can conveniently instantiate an array of objects from the PLC where each item represents a device with different properties. We use them to group a set of controls and behaviors together in a re-usable way.

A user control can be designed by the following steps: We shall show the user control creation in the context of our home automation project.

In this example, the fictitious house automation has a Lock (a device that can be used to lock the door), MainDoor (the main gate to enter the yard), and a water valve. All these devices have a name and IsClosed boolean variables. These common properties will be displayed by the user control.

The sample application will have the following default view.

We are displaying four different kinds of device status in the HMI. Each device has a name, icon, status text and some symbol to indicate visual status. We have designed a single-user control and we have created four instances of it. The name and the status are reading from the PLC directly. Device images are supplied as parameters to the user control. Status symbol is loaded by JavaScript code.

How to create a user control in TwinCAT HMI Engineering

Creating a user control in the engineering environment is very easy. If we follow the STEPS and watch the video then it will be clear and we shall be able to create any kinds of usercontrol after this.

STEP 01:  Fire our Visual Studio and create a TwinCAT HMI project (File | New Project | TwinCATHMI | TwinCATHMI Project). We have the following empty HMI

STEP 02:  Create a folder in the solution explorer and name is as UserControl. Select project in the solution explorer and right click then add new folder

STEP 03:  Right click on the UserControl folder and select Add  | New Item this will bring the following screenshoot. Give a suitable name and press Add.

 The empty user control has been created where we can decorate it, we can see our control (UserControlCommonDevices.usercontrol and its parameter UserControlCommonDevices.usercontrol.json). UserControlCommonDevices.usercontrol.json is a JSON-based dialog where we can insert the needed parameters for the usercontrol.

STEP 04: Bring toolbox (View | Tool Box  or Alt + CTRL + X)

STEP 05: Decorate the user control by using required Beckhoff control as shown in the following image.

STEP 06: Give a suitable expressive name for the identifier field in the Beckhoff image control

STEP 07: Click on the UserControlCommonDevices.usercontrol.json file in the solution explorer, this will open a dialog where we can configure the parameter name, Datatype, default value etc.

STEP 08: We change the device name  Datatype to string  and DeviceImage to Path as shown in the following image.

STEP 09: Drag and drop UserControlCommonDevices.user control file from the solution explorer 4 times and arrange those side by side as shown in our final HMI image.

STEP 10: Now we need to do the server configuration (we tell HMI  from where to fetch data, in this case from our local PLC)

STEP 11: Run the PLC to local mode or to a PLC and press TwinCAT HMI | Windows | TwinCAT HMI Server configuration

STEP 12: Configure according to the image below and finally press on the accept button, if you are running the program to remote PLC see the link for more https://www.hemelix.com/automation/structured-text-how-to-add-route-for-plc/

STEP 13: Create a folder called Script in our solution explorer and rename it as Script and add a JavaScript file rename it for example JSGetValveImagePath.js

STEP 14: Copy the content from the code below and configure as shown in the following image

STEP 15: We shall pass the isClosed variable and DeviceType parameter to JavaScript function, based on this we shall find a status image for showing it to the HMI, copy paste the code to the JS function.

STEP 16: Configure the JavaScript function’s parameters as shown in the following image.

STEP 17: Copy the related images to the image folders

STEP 18: Configure each user controls by passing DevieName, DeviceImage, DeviceStatus and DeviceStatusSymbol (this is returned by the JavaScript function)

STEP 19: Press on the right side small rectangle to configure the parameters.

Download the project and code

Please download the sample and try! There are 2 projects in the solution. 1 PLC project and another HMI project. The status is displayed from the PLC to the HMI.

The JavaScript code:

// Keep these lines for a best effort IntelliSense of Visual Studio 2017 and higher.
/// <reference path="../../Packages/Beckhoff.TwinCAT.HMI.Framework.12.742.0/runtimes/native1.12-tchmi/TcHmi.d.ts" />
(function (TcHmi) {
    var Functions;
    (function (Functions) {
        var MyNamespace;
        (function (MyNamespace) {
            function JSGetValveImagePath(IsClosed, DeviceType) {
                var returnamenpath = '';
                switch (DeviceType) {
                    case 0:
                        if (IsClosed == true) {
                            returnamenpath = 'Images/pipehor_closed.svg';
                        }
                        else {
                            returnamenpath = 'Images/pipehor_open.svg';
                        }

                        break;
                    case 1:
                        if (IsClosed == true) {
                            returnamenpath = 'Images/pipever_closed.svg';
                        }
                        else {
                            returnamenpath = 'Images/pipever_open.svg';
                        }
                        break;
                    case 2:
                        if (IsClosed == true) {
                            returnamenpath = 'Images/pipever_closed.svg';
                        }
                        else {
                            returnamenpath = 'Images/pipever_open.svg';
                        }
                        break;
                    case 3:
                        if (IsClosed == true) {
                            returnamenpath = 'Images/pipehor_closed.svg';
                        }
                        else {
                            returnamenpath = 'Images/pipehor_open.svg';
                        }
                        break;
                    default:
                        break;
                }
                return returnamenpath;
            }
            MyNamespace.JSGetValveImagePath = JSGetValveImagePath;
        })(MyNamespace = Functions.MyNamespace || (Functions.MyNamespace = {}));
        Functions.registerFunctionEx('JSGetValveImagePath', 'TcHmi.Functions.MyNamespace', MyNamespace.JSGetValveImagePath);
    })(Functions = TcHmi.Functions || (TcHmi.Functions = {}));
})(TcHmi);            
           

 

The PLC Code:

//Function block header
FUNCTION_BLOCK FN_DEVICES
VAR_INPUT
Enabled : BOOL; 
DeviceType : SINT;
END_VAR
VAR_OUTPUT
Is_Closed : BOOL;
DeviceName : T_MaxString;
END_VAR
VAR
myTimer : TON;
counter : UINT := 0;
timeInterval : TIME;
END_VAR

 

//Function block body
(* If the device is enabled then the program generate IsClosed to false from true and vice versa to simulate UI and the device. Device name and their type Name type Lock 0 -> Every 200 miliseconds interval changes the status Thermometer 1 -> Not valid as there is no IsClosed variable Water Valve 2 -> Every 400 miliseconds interval changes the status MainDoor 3 -> Every 500 miliseconds interval changes the status *) CASE DeviceType OF 0: timeInterval := T#200MS; DeviceName := "Door Lock"; 1: timeInterval := T#300MS; DeviceName := "Thermometer"; ; 2: timeInterval := T#400MS; DeviceName := "Water Valve"; 3: timeInterval := T#500MS; DeviceName := "MainDoor"; END_CASE myTimer(IN:= Enabled, PT:= timeInterval); IF myTimer.Q = TRUE THEN myTimer(IN:= FALSE); IF Is_Closed = TRUE THEN Is_Closed := FALSE; ELSE Is_Closed := TRUE; END_IF END_IF

View

An application can have many views. A user can jump from one view to another view and come back to the previous view. All contents of the view are logically combined. By default, HMI engineering offers a default view, the name of the default view is “Desktop.view”. It is the landing page of any HMI system. We can load as many views as possible depending on our needs.

A view is simply the web page we see. A page that displays the text, images of a website.  If we build websites with HTML, then every page we create is the view like the homepage, about page, and the contact page. The view can be considered as a rectangle on the screen that shows some type of content.  The rectangle here is actually invisible, but every view occupies a rectangle shape. TwinCAT HMI framework format the view for us that fits the browser.

 

The view can be used for HMI navigation purposes that we shall describe in the next section.

 

We shall have 4 other views on our fictitious home automation system.

Desktop.view, default view, general info about the home automation system

Lockview.view, main house door lock status, shows lock status with a text and with an image.

Thermometerview.view, will display the current temperature of the house and allow to change the temperature by opening and closing valves for warm and cold water

WaterValveview.view, shows a page for manipulating valves directly

MainDoorview.view,  allows the yard door to open and close

Main view of the application: If we press on the door icon, it will create a new view and that is displayed on the screen. We can come back to main view by pressing on the back arrow image.

The other 3 views are shown in the following images.

Main door lock status view. We can lock and unlock the main door through the PLC. If we press on the lock then it will update the lock variable in the PLC code.

Temperature control view of the house. We can set a target value and command the PLC which will open the valve to achieve the desired room temperature.

Valve control view of the house. Valve can be opened by directly or when we are changing temperature in temperature control view.

Design of the application

STEP 01:

=>Create an HMI project as usual (File | New | Project | Select HMI ) or see the earlier HMI project

=>This will create the default page (or view Desktop.view)

=>Create two buttons (Change the text to “Go To Lock Status View” and “Go To YardDoor Status View”)

=>Change the IDs of these button to TcHmi_Controls_Beckhoff_TcHmiButton_LockStatusView and TcHmi_Controls_Beckhoff_TcHmiButton_YardGateView , it is good to have the same ID, because we can refer to any control by the ID.

=>Add four images at the bottom (you have to add the images to the images folder and configure it) by drag and dropping four times the image control.

=>Press F4 or properties for each image and configure the Src field.

=>Now add the following code as embedded JavaScript code under the 2 desktop buttons and change the view which one is going to load, the following load the LockStatus.view for example.

var v1 = TcHmi.View.get();
console.log('Button pressed ' + v1.getId());
TcHmi.View.load('LockStatus.view', function (data) {
var v2 = TcHmi.View.get();
console.log(v2.getId());
});

 

STEP 02:

=> Now add 4 views (Project | Add new item | View) and rename it to LockStatus.view, ThermometerStatus.view, Watervalve.view and YardDoor.view

=> Now if we press on the 2 buttons then it will load 2 views according to STEP 01 (but of course we don’t have any way to come back to Desktop.view)

 

STEP 03:

 

We are using the  following embedded JavaScript for loading LockStatus.view  and YardDoorStatus.view (under two buttons in the Desktop.view) just to show how to use behind a button.

=>Select those two button |Properties (F4) | Event | Add JavaScript | Edit Source Code and enter the following code

=>Test the work so far done using the live view (press the 2 buttons and see that we are able to switch view)

var v1 = TcHmi.View.get();
console.log('Button pressed ' + v1.getId());
TcHmi.View.load('LockStatus.view', function (data) {
var v2 = TcHmi.View.get();
console.log(v2.getId());
});

STEP 04:

=>Insert all the necessary control to each view, pay attention to the ID of the control.

=>Insert an image control as back arrow, each back arrow should be name in such a way that match the code behind file. For example see the event TcHmi_Controls_Beckhoff_TcHmiImage_LockToDesktop.onPressed in the code behind file. The back image control ID is TcHmi_Controls_Beckhoff_TcHmiImage_LockToDesktop

=>When all IDs are OK and desired control are in places we can insert the following code behind file in the Script folder.

=>We are using some minor JavaScript function such as FloatToText and TextToFloat

 

Code behind file for loading the views:

The view are loaded by a code behind file. We shall define a function for each icon in the code behind file as shown in the following code: When we press on the icon (images at the bottom) then we load the view by the following code.

 
// Keep these lines for a best effort IntelliSense of Visual Studio 2017.
/// <reference path="../../Packages/Beckhoff.TwinCAT.HMI.Framework.12.742.5/runtimes/native1.12-tchmi/TcHmi.d.ts" />

(function (/** @type {globalThis.TcHmi} */ TcHmi) {
    // If you want to unregister an event outside the event code you need to use the return value of the method register()
    var destroyOnInitialized = TcHmi.EventProvider.register('onInitialized', function (e, data) {
        // This event will be raised only once, so we can free resources. 
        // It's best practice to use destroy function of the event object within the callback function to avoid conflicts.
        e.destroy();


        //1 Manage LockStatus view  to Desktop view
        // put this function block before the previous one otherwise it not work.
        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_LockToDesktop.onPressed',
            function (evt, data) {
                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('Desktop.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });
                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );


        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_DesktopLock.onPressed',
            function (evt, data) {

                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('LockStatus.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });

                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );


        //2 Manage Thermometer view  to Desktop view
        // put this function block before the previous one otherwise it not work.
        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_ThermoToDesktop.onPressed',
            function (evt, data) {
                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('Desktop.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });
                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );


        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_DesktopThermometer.onPressed',
            function (evt, data) {

                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('ThermometerStatus.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });

                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );

        //3 Manage Water Valve view  to Desktop view
        // put this function block before the previous one otherwise it not work.
        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_WaterValveToDesktop.onPressed',
            function (evt, data) {
                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('Desktop.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });
                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );


        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_DesktopValve.onPressed',
            function (evt, data) {

                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('WaterValve.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });

                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );

        //4 Manage Yard Door view  to Desktop view
        // put this function block before the previous one otherwise it not work.
        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_YardDoorToDesktop.onPressed',
            function (evt, data) {
                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('Desktop.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });
                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );


        var destroyEvent = TcHmi.EventProvider.register(
            'TcHmi_Controls_Beckhoff_TcHmiImage_DesktopYardDoor.onPressed',
            function (evt, data) {

                var v1 = TcHmi.View.get();
                console.log(v1.getId());
                TcHmi.View.load('YardDoor.view', function (data) {
                    var v2 = TcHmi.View.get();
                    console.log(v2.getId());
                });

                // Destroy to free event resources if event is no longer needed.
                //But comment out if you need it
                //destroyEvent();
            }
        );
    });
})(TcHmi);

 

 

 

 Final STEP
=> Test with live view or  review and compare the code if something goes wrong.

 

TIP 01: Button, Image Id in different view should have a unique name

TIP 02: If we want to have back and forth in the navigation then we should keep stuff in the memory by not destroying the event (comment out  destroyEvent())

TIP 03: We might need to reorganize the JavaScript function location inside the file for reference

TIP 04: These are done by JavaScript, so no event is configured in the image properties see more at https://www.w3schools.com/js/default.asp

Down load the source code from the link below.

Region and Content

Region

The region is a container for content controls. The content to be displayed can be defined using the Target Content attribute. We can divide the screen into many regions as necessary. The main advantage of the region is better visibility and better user experiences. We can show or display it in the related regions. One of the main use cases is, for example, we want to show a static button on the left side all the time but the content of the right side changes as we press some other button to update the device status. Let take a look at our fictitious house automation system again as shown in the following image. As we see, we have at the top Status window where we want to display the status of the device. In the left side, we have some sample button that does not change whatever we show in the pink area. At the bottom we have some images, if we click on any of this image then related content are shown in the content area.

How to add region in visual studio:

=> From toolbox  select region from the category HMI: System and drop it into the desktop area

=> Change the properties of the region such as width, height, color (press F4 to get the property window)

If we press on the gate icon then the related status is shown on the top status area and the left hand side button remains there. The buttons are located in different region.

Content

A content control is an independent container object that can be loaded in a region. This control can be used, for example, 

to subdivide operating concepts into various contents. It is created under the TcHmi project tree with the context menu Add new Item.

How to create content in the visual studio:

=> Create a folder in the VS solution such as Contents

=> In the new folder, select Add | New Item  (rename it as it fits)

=> Decorate the new content page as necessary, we have 4 content pages in our example.

=> We inserted 4 images in the bottom area where we can click on an image and then we show the related content in the content area. In this case, we modify the properties of the image event (onPressed event)

Here is how the technique that shows how do we modify the content of the content area.

We shall introduce YouTube video soon that shows Step by Step

HMI Navigation

The desktop view is the start page for any application on which we can create by using Visual Studio Wizard for visualization purposes. There are no hard rules for how we design the user interface. Look and feel, usability are the different factors that define how the UI will be structured.
A good method of increasing the tidiness is to split the visualization into various parts. To do this, divide it into various content objects.
These content objects can be displayed in different areas of the desktop view. For example, the visualization could be divided into three areas in the desktop view: a status bar, a content area, and a navigation bar area. Various content objects can be displayed in the content area during the runtime.
The contents of the status bar area and the contents of the navigation bar area can also be changed as needed. We shall design the HMI for our fictitious home automation system. The first page will show the logo and there will be navigation at the bottom. 

More information at 

https://infosys.beckhoff.com/english.php?content=../content/1033/te2000_tc3_hmi_engineering/3477213067.html&id=3087192027615641287

https://www.youtube.com/watch?v=72PBZYwMEfE