JavaScript, function and Code Behind

 We can manipulate any properties of control by JavaScript.  JavaScript has arisen in TwinCAT HMI in two forms. 

A. Function

B. Code Behind

How to change image by JavaScript

In this exercise, we shall change the image source by using JavaScript. if we insert an image in the text box then a source name is returned by JavaScript as shown in the code snippet.

(function (TcHmi) {
var FunctionJS1 = function (par1) {
switch (par1) {
case 1:
return 'Images/valve.svg';
break;
case 2:
return 'Images/valve_hor_closed.svg';
break;
case 3:
return 'Images/Beckhoff_Logo.svg';
break;
case 4:
return 'Images/lock.svg';
break;
case 5:
return 'Images/thermometer.svg';
break;
case 6:
return 'Images/valve_ver_open.svg';
break;
default:
return 'Images/MainDoor.svg';
}
};
TcHmi.Functions.registerFunction('FunctionJS1', FunctionJS1);
})(TcHmi);

Type 1, 2, etc and see the image is being changed as shown in the JavaScript code. In the first pair, the image source has been retrieved by the following statement.  FunctionJS1(%ctrl%TextID::Text%/ctrl%)

TIP 01:

If we insert a JavaScript function in the project and later we rename the function, it is not enough to change the name in solution explorer. Intellisense gets the name from some JSON file, we need to change the name of the JSON file.

TIP 02: When we have our JavaScript function that will be available in Actions and conditions (Functions | Other). This function can be drag and drop for execution. This is shown in the following figure. This can be done also by copy-paste from note pad (for example you want to copy from another project)

Java Script Functions

JavaScript plays a vital role in TwinCAT HMI development. You can make the HMI elegant and beautiful by using JavaScript by using properly.

Image rotation, rotation angle generated by PLC

We shall describe the example with a video tutorial very soon. You can download the example and see how to use the rotation function in HMI. Click on the page to download it https://www.hemelix.com/rotationtest/

The green dot is the center of rotation. Beckhoff icon is rotating around the center of the image itself and on the other hand, the rectangle is rotating around a corner of the image. The angle is generated by PLC code (as current value 354 degrees). The Start button will start the rotation and the Stop button will stop.

The Beckhoff image is rotated by the following DIV element directly added to the HTML editor

    <div id="TcHmiImage" data-tchmi-type="tchmi-image" data-tchmi-height="272" data-tchmi-height-unit="px" data-tchmi-left="10" data-tchmi-left-unit="px" data-tchmi-top="36" data-tchmi-top-unit="px" data-tchmi-width="364" data-tchmi-width-unit="px" data-tchmi-src="%pp%Icon%/pp%" data-tchmi-transform="%f%DegreesToRotationWithValveEnable(%pp%Value%/pp%)%/f%">
    </div>

The corresponding JavaScript function is as follows:

DegreesToRotationWithValveEnable JS function is called with a parameter (rotation angle, Degrees). The angle in this example is generated by the PLC code (by some INT type)

(function (TcHmi) {
    var DegreesToRotationWithValveEnable = function (Degrees) {
        return [{
            "transformType": "Rotate",
            "angle": Degrees,
            "angleUnit": "deg"
        }]
    };
    TcHmi.Functions.registerFunction('DegreesToRotationWithValveEnable', DegreesToRotationWithValveEnable);
})(TcHmi);
 

Now we explain how do we rotate the rectangle. A different transformation is used for this rectangle rotation. When the angle is changed we call a transformation as shown in the following image. This angle is generated by PLC code as well.

The corresponding JavaScript function is shown in the following code.

(function (TcHmi) {
    var FunctionJSRotateAround = function (Object, X, Y, Z, Angle) {
        var t = [{
            "transformType": "Origin",
            "x": X,
            "xUnit": "px",
            "y": Y,
            "yUnit": "px",
            "z": Z,
            "zUnit": "px",
        }, {
            "transformType": "Rotate",
            "angle": Angle,
            "angleUnit": "deg"
        }
        ];
        Object.setTransform(t);
    };
    TcHmi.Functions.registerFunction('FunctionJSRotateAround', FunctionJSRotateAround);
})(TcHmi);

For second Beckhoff image we are using center of image (though hard coded) as shown in the following image.

Feel free to download  and try the example, if there is any issue please contact info@heme…

Image rotation, rotation angle generated by JavaScript

We described the image in the previous section where the rotation angle was generated by the PLC code. The situation could be for example a device is rotating and we know it’s RPM and we can map it for the HMI. 

But in other cases where we don’t know the RPM how do we animate the rotation? In this sample, we are going to solve it. In this sample, we know that the device is rotating by some Boolean variable. We can map the variable and create a custom notifier for that. When the variable is true then some images start to rotate and when it is false then it will stop to rotate. The following snippet shows the variable startRotation.  If startRotation  is true in the PLC then Beckhoff icon will rotate and if it is false then the rotation stops.

PROGRAM MAIN
VAR
startRotation :  BOOL:= FALSE; //ON image will rotate, OFF image will stop
myTimer : TON; // Timer for simulating the alarm as it is coming from devices
counter : DINT :=0;
END_VAR

You can download the example and see how to use the rotation function in HMI. Click on the page to download it https://www.hemelix.com/rotationtest2/

Select the startRotation in the custom event.

The corresponding JavaScript function (FunctionJS_ControlManipulation) is as follows:

 

(function (TcHmi) {
    var FunctionJS_ControlManipulation = function () {
        var Angle = 0;
        var timer = 0;
        var timer_is_on = 0;
        var h;
        var someControl = TcHmi.Controls.get('ViewDesktopBeckhoffLogo');
        startCount();

        function timedCount() {
            Angle = Angle + 2;
            timer = setTimeout(timedCount, 10);
            if (Angle > 360) {
                Angle = 0;
            }

            TcHmi.Symbol.readEx2('%s%PLC1.MAIN.startRotation%/s%', function (data) {
                if (data.error === TcHmi.Errors.NONE) {
                    var value = data.value;
                    if (value == false) {
                        Angle = 0;
                        h = [{
                            "transformType": "Origin",
                            "x": 100,
                            "xUnit": "px",
                            "y": 25,
                            "yUnit": "px",
                            "z": 0,
                            "zUnit": "px",
                        }, {
                            "transformType": "Rotate",
                            "angle": Angle,
                            "angleUnit": "deg"
                        }
                        ];
                        someControl.setTransform(h);
                        stopCount();
                    }
                } else {
                    console.log("Error reading variable startTimer");
                    stopCount();
                }
            });
            h = [{
                "transformType": "Origin",
                "x": 100,
                "xUnit": "px",
                "y": 25,
                "yUnit": "px",
                "z": 0,
                "zUnit": "px",
            }, {
                "transformType": "Rotate",
                "angle": Angle,
                "angleUnit": "deg"
            }
            ];
            someControl.setTransform(h);
        }
        function startCount() {
            if (!timer_is_on) {
                timer_is_on = 1;
                timedCount();
            }
        }
        function stopCount() {
            clearTimeout(timer);
            timer_is_on = 0;
        }
    };
    TcHmi.Functions.registerFunction('FunctionJS_ControlManipulation', FunctionJS_ControlManipulation);
})(TcHmi);
 

Feel free to download  and try the example, if there is any issue please contact info@heme…

Filling a tank with liquid

This is an off-topic example (that does not belong to our fictitious home automation project). We are including here as we get some requests from the readers. The requirement of the tutorial is like these.

A tank will be filled with liquid. There will be a valve and a proximity sensor (upper side). The proximity sensor will indicate that the tank is full so the valve will be closed. There will be a proximity sensor in the bottom of the tank that will indicate the tank is empty via an out valve. There will be a Fill/Empty button. If the tank is full then it can be emptied by pressing on Empty button and vice versa.

The settings of the valve can be done by opening a popup (not shown here).

 

The tank is filled by the following two JavaScript files.


(function (TcHmi) {
    var FunctionJS_DefaultTankHeight = function (height, fill) {
        var intfill = Math.floor(fill);
        var updatedHeight = parseInt(height / 100 * intfill);
        return updatedHeight;
    };
    TcHmi.Functions.registerFunction('FunctionJS_DefaultTankHeight', FunctionJS_DefaultTankHeight);
})(TcHmi);

 


(function (TcHmi) {
    var FunctionJS_FindTankTop = function (height,fill,top) {
        var intfill = Math.floor(fill);
        var data = parseInt(top + height - ((height / 100) * intfill));
        return data;
    };    
    TcHmi.Functions.registerFunction('FunctionJS_FindTankTop', FunctionJS_FindTankTop);
})(TcHmi);

 

Read Write Symbol by JavaScript

A symbol can be read or can be written with a value by using JavaScript. This example shows how to read value from PLC and how to write value to PLC. The sample application is shown in the following image.

Read button read the value from a symbol and write button write the value to the symbol. Read button read a value by JavaScript, following code shows the JavaScript function for reading real data.

(function (TcHmi) {
    var FunctionJS_ReadWriteReal = function (RealData, IsReading) {
        var retValue = 0;
        var formattedSymbol = '%s%PLC1.MAIN.realData%/s%';
        //IsReading == false is not used in actual call.
        //The write function is used by directly with the write button
        if (IsReading == false) {
            TcHmi.Symbol.writeEx(formattedSymbol, IntegerData, function (data) {
                if (data.error === TcHmi.Errors.NONE) {
                    console.log("Writting real OK");
                } else {
                    console.log("Writting real NOT OK");
                }
            });
        } else {
            TcHmi.Symbol.readEx2(formattedSymbol, function (data) {
                if (data.error === TcHmi.Errors.NONE) {
                    retValue = data.value;
                    console.log("Reading real OK");
                    console.log(retValue);
                    var targetControl = TcHmi.Controls.get("TcHmiTextbox_Real");
                    if (targetControl != undefined) {
                        targetControl.setText(retValue.toString());
                    }
                } else {
                    console.log("Reading real NOT OK");
                }
            });
        }
        return retValue;
    };
    TcHmi.Functions.registerFunction('FunctionJS_ReadWriteReal', FunctionJS_ReadWriteReal);
})(TcHmi);

When we press the write button for writing real data then the following code will be executed.

The JavaScript code in text format:


var targetControl = TcHmi.Controls.get("TcHmiTextbox_Real");
if (targetControl != undefined) {         var top = parseFloat(targetControl.getText(), 10);         console.log(top);         var formattedSymbol = '%s%PLC1.MAIN.realData%/s%';             TcHmi.Symbol.writeEx(formattedSymbol, top, function (data) {                 if (data.error === TcHmi.Errors.NONE) {                     console.log("Writting Real OK");                 } else {                     console.log("Writting Real NOT OK");                     console.log(data.error);                     console.log(data.response);                 }             });     }


How to Show Popup and cancel it automatically

This section shows how to show pop up when some interesting events happen. This will explain how to show the dialog and cancel the dialog after 5 seconds. The concept can be used to shows when some variable changes its status.

In this sample, we have 3 PLC variables and when it is true then it displays a note for 5 seconds. The variables are changed back to false (by JavaScript so that the note can be shown again).

First, we have created a simple PLC program that will simulate 3 Boolean variables and a variable for the starting timer. The timer and a local counter will set those 3 variables if those are false. The source code can be downloaded by clicking on the link in this section.

Simple PLC Program for generating signal:

PROGRAM MAIN
VAR
alarmList :  BOOL:= FALSE;
warningList :  BOOL:= FALSE;
infoList :  BOOL:= FALSE;
myTimer : TON; // Timer for simulating the alarm as it is coming from devices
startTimer : BOOL := FALSE; // From UI we press to start the timer
counter : DINT :=0;
END_VAR
myTimer(IN:= startTimer, PT:=T#1S);
IF myTimer.Q = TRUE THEN
myTimer(IN:= FALSE);
counter:= counter +1;
END_IF
IF ( ( counter MOD 30 ) = 10 ) AND NOT alarmList THEN
alarmList:= TRUE;
END_IF
IF ( ( counter MOD 30 ) = 20 ) AND NOT warningList THEN
warningList:= TRUE;
END_IF
IF ( ( counter MOD 30 ) = 29 ) AND NOT infoList THEN
infoList:= TRUE;
END_IF
IF counter > 200000 THEN
counter:= 0;
END_IF

When any of the variables is true then an info note is shown. That note is killed by JavaScript after 10 seconds and the corresponding variable is set back to false, so a new note can be shown (just for demo purposes).

How this has been designed:

 

A circle and rectangle with a suitable layout have been used. FillColor and StrokeColor will be updated during initialization based on the notification type as shown below.

One of the important issue I found is notification type. Based on same notification type, I want to display 3 different kinds of note. This Notification type is passed to the JavaScript function which creates the note. The same notification type is used during the onAttached event. See the PopupOpen JavaScript function. Control properties come from the parameters name, for example data-tchmi-popupnotificationmessag (in the code below) is notification message parameter all in small font.

        popupParametersPopupControl['data-tchmi-height'] = Height;
        popupParametersPopupControl['data-tchmi-width'] = Width;
        popupParametersPopupControl['data-tchmi-zindex'] = "1000";
        popupParametersPopupControl['data-tchmi-notificationtype'] = NotificationType;
        popupParametersPopupControl['data-tchmi-popupnotificationdevicename'] = Header;
        popupParametersPopupControl['data-tchmi-popupnotificationmessage'] = Message;

Following is the complete function that does the magic. Read the comment in the code to make it clear.

 

(function (TcHmi) {

    var PopupOpen = function (Content,Header,Message,NotificationType,HorizontalAlignment,VerticalAlignment,HorizontalDistance,VerticalDistance,Height,Width,WhereToInsert,AddWhereclicked) {
        if (TcHmi.Controls.get(String('PopupInstance'+Content)) || Content == null) {
            return;
        }
        var PopupUuid = String("Popup_" + TcHmiExampleCollection.Utilities.generateUuidv4());
        var PopParameterStartIndex;
        var popupParametersPopupControl = [];
        var PopParameters = [];
        var popup;
        var popupType = Content.split(".");
        popupType = popupType[popupType.length - 1].toLowerCase();

        if (Content == undefined) { Content = "" }
        if (HorizontalAlignment == undefined) { HorizontalAlignment = "Top" }
        if (VerticalAlignment == undefined) { VerticalAlignment = "Left" }
        if (HorizontalDistance == undefined) { HorizontalDistance = 0 }
        if (VerticalDistance == undefined) { VerticalDistance = 0 }
        if (Height == undefined) { Height = 100 }
        if (Width == undefined) { Width = 100 }
        if (WhereToInsert == undefined) { WhereToInsert = "Desktop" }

        if (HorizontalAlignment == 'Left') {
            popupParametersPopupControl['data-tchmi-left'] = HorizontalDistance;
        }
        else if (HorizontalAlignment == 'Center') {
            TcHmi.Log.warn('Please use left/right');
        }
        else if (HorizontalAlignment == 'Right') {
            popupParametersPopupControl['data-tchmi-right'] = HorizontalDistance;
        }
        
        if (VerticalAlignment == 'Top') {
            popupParametersPopupControl['data-tchmi-top'] = VerticalDistance;
        }
        if (VerticalAlignment == 'Middle') {
            TcHmi.Log.warn('Please use left/right');
        }
        if (VerticalAlignment == 'Bottom') {
            popupParametersPopupControl['data-tchmi-bottom'] = VerticalDistance;
        }

        /// For clicking consider using the following code
        if (AddWhereclicked == true) {
            popupParametersPopupControl['data-tchmi-left'] = TcHmiExampleCollection.Utilities.ClickPressCoordinates.x;
            popupParametersPopupControl['data-tchmi-top'] = TcHmiExampleCollection.Utilities.ClickPressCoordinates.y;
        }

        /// For automatic notification generating use the following code that will read a control and set the note
        //Read the location of the circle and position the note to right place
        switch (NotificationType) {
            case 1:
                var targetControlCircleAlarm = TcHmi.Controls.get("TcHmiEllipse_Alarm");
                if (targetControlCircleAlarm != undefined) {
                    var top = targetControlCircleAlarm.getTop();
                    var left = targetControlCircleAlarm.getLeft();
                    popupParametersPopupControl['data-tchmi-left'] = left;
                    popupParametersPopupControl['data-tchmi-top'] = top-25;
                }
                break;
            case 2:
                var targetControlCircleWarning = TcHmi.Controls.get("TcHmiEllipse_Warning");
                if (targetControlCircleWarning != undefined) {
                    var top = targetControlCircleWarning.getTop();
                    var left = targetControlCircleWarning.getLeft();
                    popupParametersPopupControl['data-tchmi-left'] = left;
                    popupParametersPopupControl['data-tchmi-top'] = top-25;
                }
                break;
            case 3:
                var targetControlCircleInfo = TcHmi.Controls.get("TcHmiEllipse_Info");
                if (targetControlCircleInfo != undefined) {
                    var top = targetControlCircleInfo.getTop();
                    var left = targetControlCircleInfo.getLeft();
                    popupParametersPopupControl['data-tchmi-left'] = left;
                    popupParametersPopupControl['data-tchmi-top'] = top-25;
                }
                break;
            default:
                var targetControlCircleAlarmDefault = TcHmi.Controls.get("TcHmiEllipse_Alarm");
                if (targetControlCircleAlarmDefault != undefined) {
                    var top = targetControlCircleAlarmDefault.getTop();
                    var left = targetControlCircleAlarmDefault.getLeft();
                    popupParametersPopupControl['data-tchmi-left'] = left;
                    popupParametersPopupControl['data-tchmi-top'] = top-25;
                }
        }

        popupParametersPopupControl['data-tchmi-height'] = Height;
        popupParametersPopupControl['data-tchmi-width'] = Width;
        popupParametersPopupControl['data-tchmi-zindex'] = "1000";
        popupParametersPopupControl['data-tchmi-notificationtype'] = NotificationType;
        popupParametersPopupControl['data-tchmi-popupnotificationdevicename'] = Header; // Comes as parameter
        popupParametersPopupControl['data-tchmi-popupnotificationmessage'] = Message;   // Comes as parameter
        
        var documentSizeX = document.body.clientWidth;
        var documentSizeY = document.body.clientHeight;

        if (popupType == "usercontrol") {
            popupParametersPopupControl['data-tchmi-target-user-control'] = Content;
            popup = TcHmi.ControlFactory.createEx(
            'tchmi-user-control-host',
            PopupUuid,
            popupParametersPopupControl
            );
        }
        else if (popupType == "content") {
            popupParametersPopupControl['data-tchmi-target-content'] = Content;
            popup = TcHmi.ControlFactory.createEx(
            'tchmi-region',
            PopupUuid,
            popupParametersPopupControl
            );
        }
        
        var targetControlCircle = TcHmi.Controls.get("TcHmiEllipse_UserControl_ShowNote");
        if (targetControlCircle != undefined) {
            console.log("note found");
        }

        var desktop = TcHmi.Controls.get(WhereToInsert.__id);

        if (desktop && popup ) {
            desktop.addChild(popup);
            TcHmiExampleCollection.Utilities.dragElement(document.getElementById(PopupUuid));
        }

        var c = 0;
        var t;
        var timer_is_on = 0;

        function timedCount() {
            c = c + 1;
            t = setTimeout(timedCount, 1000);
            if (c == 10) {
                myStopFunction(); //Reset variables in PLC
                stopCount(); //Destroy the timer and release
            }
        }

        function startCount() {
            if (!timer_is_on) {
                timer_is_on = 1;
                timedCount();
            }
        }

        function stopCount() {
            clearTimeout(t);
            timer_is_on = 0;
            c = 0;
        }

        startCount();

        function myStopFunction() {
            var myoldcontrol = TcHmi.Controls.get(popup.__id).destroy();
            if (myoldcontrol == undefined) {
                console.log("not Found ");
            } else {
                console.log("Found ");
            }

            if (NotificationType == 1) { //Write to the PLC variable to false, it was set true by timer
                TcHmi.Symbol.writeEx('%s%PLC1.MAIN.alarmList%/s%', false, function (data) {
                    if (data.error === TcHmi.Errors.NONE) {
                        console.log("alarmList Write OK");
                    } else {
                        console.log("alarmList Write NOT OK");
                    }
                });
            }

            if (NotificationType == 2) {  //Write to the PLC variable to false, it was set true by timer
                TcHmi.Symbol.writeEx('%s%PLC1.MAIN.warningList%/s%', false, function (data) {
                    if (data.error === TcHmi.Errors.NONE) {
                        console.log("warningList Write OK");
                    } else {
                        console.log("warningList Write NOT OK");
                    }
                });
            }
            if (NotificationType == 3) {  //Write to the PLC variable to false, it was set true by timer
                TcHmi.Symbol.writeEx('%s%PLC1.MAIN.infoList%/s%', false, function (data) {
                    if (data.error === TcHmi.Errors.NONE) {
                        console.log("infoList Write OK");
                    } else {
                        console.log("infoList Write NOT OK");
                    }
                });
            }
        } //myStopFunction
    };    
    TcHmi.Functions.registerFunction('PopupOpen', PopupOpen); //TwinCAT
})(TcHmi);

Some background about JS Object, JSON, JSON object and JSON string

Basic JS object rules:

=> JSON names require double quotes. JavaScript names do not.

    var car = { type: "Fiat", model: "500", color: "white" }; //car is not valid JSON but a valid JS object
    var carstring = JSON.stringify(car); // convert object to string
    {"type":"Fiat","model":"500","color":"white"} //carstring is valid JSON which can be tested byJSONLint
    var JSONObject = JSON.parse(carstring); //String to object
    console.log(JSONObject.type);  //Fiat

Basic JSON rules:

=>JSON is text, and we can convert any JavaScript object into JSON, and send JSON to the server.

=>Data is in name/value pairs

=>Data is separated by commas

=>Curly braces hold objects

=>Square brackets hold arrays

    //Array example
    employees = [
        { "firstName": "John", "lastName": "Doe" },
        { "firstName": "Anna", "lastName": "Smith" },
        { "firstName": "Peter", "lastName": "Jones" }
    ];
     console.log(employees[0]); //print first object in the array
     console.log(employees[0].firstName);  //print first name
      var myJSON = '{"name":"John", "age":31, "city":"New York"}';
      var myObj = JSON.parse(myJSON); //get an JS object
      console.log(myObj); //print the JS object
      console.log(myObj.age); //Print age, 31

JSON data is written as name/value pairs.

A name/value pair consists of a field name (in double quotes), followed by a colon, followed by a value:

JSON Example:

{"name":"John"}

In JavaScript, keys can be strings, numbers, or identifier names:

var obj = { "name": "John" };
console.log(obj.name); //print John

Additional example:

Say we have a Grid view and we want to extract data when user click on a cell. First we need to tell the framework about the column information as shown in the following image. There are 2 column in the following grid example. Each column has name, level and control (edit box, text block etc). We access the property based on name (productid, storeid in this example). SrcData is pure JSON string and there should be productid, storeid as keys in the JSON string.


[
  {
    "productid": "2",  //This is matching with the Name field identifier (productid)
    "storeid": "3" //This is matching with the Name field identifier (storeid)
  },
  {
    "productid": "4",
    "storeid": "5"
  },
  {
    "productid": "6",
    "storeid": "7"
  }
]

By using following code we can extract data when user has clicked on any cell (there are some additional test for fun). When selected item event has emitted then we can call a JavaScript method with the row and column number as parameters.

        var myControl = TcHmi.Controls.get('TcHmiDatagrid_StoreProduct_1');
        if (myControl) {
            console.log(SelectedRow);
            console.log(SelectedColumn);
            var dataa = myControl.getSrcData();
            var myJSONString = JSON.stringify(dataa);
            console.log(myJSONString);
            var JSONObject = JSON.parse(myJSONString);
            var c = dataa["productid"];
            console.log(dataa[SelectedRow].productid);
            console.log(dataa[SelectedRow].storeid);
            var employees = [
                { "firstName": "John", "lastName": "Doe" },
                { "firstName": "Anna", "lastName": "Smith" },
                { "firstName": "Peter", "lastName": "Jones" }
            ];
            console.log(employees[0]); //print first object in the array
            console.log(employees[0].firstName);  //print first name John
            //var obj = JSON.parse(dataa);  //dataa is already a JS object, it will throw an exception
            console.log('new test');
            var myJSON = '{"name":"John", "age":31, "city":"New York"}';
            var myObj = JSON.parse(myJSON);
            console.log(myObj); //print the JS object
            console.log(myObj.age); //Print age, 31
        }