JavaScript (basic to advance) for TwinCAT HMI

JavaScript Function:

A JavaScript function is a block of code designed to perform a particular task. A JavaScript function is executed when “something” invokes it (calls it). An example of a JavaScript function :

let hello = function() {
  return "Hello World!";
}

We just call the method in the following way:

console.log(hello ());  //it will print Hello World! in the console

JavaScript function with Parameters:

let hello = function(st1, st2) {
  return "Hello World!" + st1 + st2;

}

When we call the above method, it will print Hello World! for TwinCAT HMI

console.log(hello(' for', ' TwinCAT HMI')); //it will print Hello World! for TwinCAT HMI in the console

 JavaScript Arrow function:

The above function can be shortened by using the arrow function in the following way with the same output.

let hello = (st1, st2) => {
  return "Hello World!" + st1 + st2;
}
console.log(hello(' for', ' TwinCAT HMI')); //same output as before

All the above functions are synchronous JavaScript functions. All these functions are executed in sequence. If a function call takes a long time then UI will be irresponsive. We can insert the following code in the HMI JavaScript editor and test it.

 

Anonymous functions:

In JavaScript, anonymous functions (function expression) are those that are produced without a name or identifier to refer to them; they are defined as :

 myfunc = (function () {  // there is no name between function and ()
   console.log('inside anonymous function');
});
myfunc();  // output inside anonymous function

Named functions:

Normal functions (function declaration) having a name or identifier to refer to them are referred to as named functions and are defined as:

function displayMessage(){
console.log('inside normalfunction');
}
displayMessage();

Using anonymous functions as arguments:

In practice, you often pass anonymous functions as arguments to other functions. For example:

setTimeout(function() {
    console.log('Execute later after 1 second')
}, 1000);

setTimeout takes 2 parameters, first one a anonymous function and second one is the timeout value.

In this example, we pass an anonymous function into the setTimeout() function. The setTimeout() function executes this anonymous function one second later.

Asynchronous JavaScript function:

In real life, things happen asynchronously. For example, when we ask for web pages from a server, it may take some time. The web engine will call or notify the browser by an event. An event handler is a particular type of callback. A callback is just a function that’s passed into another function, with the expectation that the callback will be called at the appropriate time.

An example of an asynchronous function:

setTimeout(function() {
  myFunction("I love TwinCAT HMI!");
},
3000);
function myFunction(value) {
  console.log(value); //output in the console, I love TwinCAT HMI!
}

 

Some server calls (getting data from the HMI Server or the PLC ) examples:

Reading data from the PLC may be successful or it may fail. In either case, it will be called sometimes later. If we want to use the result or if we want to handle the error we have to put our code in the appropriate places. If we have a few similar calls then it will be messy. So we try to synchronize the call as we are calling synchronous function.

var symbol = new TcHmi.Symbol('%s%PLC1.MAIN.EmptyArray%/s%');
symbol.readEx(function(data) {
  if (data.error === TcHmi.Errors.NONE) {
    var value1 = data.value;
    var myJSONString1 = JSON.stringify(value1);
    //Success, do something with value1
  } else {
    console.log('ReadEx error');
    //Error, handle it
  }
});

 

The following code uses a Promise object which takes a function as a parameter. The parameter function takes two arguments, the resolve and reject function. When a function was called successfully then it will call resolve and in the error case, it will call reject after 3 seconds.

let completed = true; // set true or false to test
let learnTC = new Promise(function (resolve, reject) {
    setTimeout(() => {
        if (completed) {
            resolve("I have learned TwinCAT HMI");
        } else {
            reject("I haven't learned TwinCAT HMI");
        }
    }, 3 * 1000);
});
learnTC .then((value) => {
  console.log(value);
  // expected output: I have learned TwinCAT HMI.
}).catch((err) => {
  console.log(err);
  // expected output: I haven't learned TwinCAT HMI.
});

 

Lets modify the code a bit, when we have call a function from server it can be OK (gotData is true) or it can fails (gotData is false). The result what has been push by resolve or reject call will be available in the caller.

let gotData = false;
function resolveORRejectAfter5Seconds() {
  return new Promise((resolve, reject) =>{
    setTimeout(() =>{
      if (gotData) {
        resolve('resolved');
      } else {
        resolve('rejected');
      }
    },
    5000);
  });
}
async
function asyncCall() {
  console.log('calling');
  const result = await resolveORRejectAfter5Seconds();
  console.log(result);
  // expected output: "resolved" or "rejected" after 5 seconds based on value of gotData
}
asyncCall();

 

Asynchronous call as synchronous:

We shall read some data from PLC asynchronously but we shall wait for the result in a synchronous way, so we can continue as usual.

function AnotherRealFunction() {
  return new Promise((resolve, reject) =>{
    var symbol = new TcHmi.Symbol('%s%PLC1.MAIN.EmptyArray%/s%');
    symbol.readEx(function(data) {
      if (data.error === TcHmi.Errors.NONE) {
        var value1 = data.value;
        var myJSONString1 = JSON.stringify(value1);
        resolve(myJSONString1);
      } else {
        reject('Error to read PLC data');
      }
    });
  });
}
async function asyncMyCall() {
  try {
    const result = await AnotherRealFunction();
    console.log(result);
  }
  catch(error) {
    console.log(`error occured = $ {error}`);
  }
}
asyncMyCall();

 

If the variable  can be read then it will print the data in the console, if it can’t read data then it will print 

error occured = Error to read PLC data

var, let and const

var english = "Hello there!";
let french = "Bonjour!";
const german = "Hallo!";

 

var:

var is function scoped when it is declared within a function. This means that it is available and can be accessed only within that function.

var tester = "hey hi";
function newFunction() {
  var hello = "hello";
}
console.log(hello); 
//Error in browser console
Reason: An uncaught exception occurred in the processing of a JavaScript action
Exception: ReferenceError: hello is not defined
Domain: TcHmi.System.TriggerManager 

 

Declare and redeclare variable with var:

var greeter = "hey hi";
var greeter = "say Hello instead";
 
var greeter = "hey hi";
greeter = "say Hello instead"; 

 

Let:

let is now preferred for variable declaration. It’s no surprise as it comes as an improvement to var declarations.

let is block scoped

 

A block is a chunk of code bounded by {}. A block lives in curly braces. Anything within curly braces is a block.

let can be updated but not re-declared.

Just like var,  a variable declared with let can be updated within its scope. Unlike var, a let variable cannot be re-declared within its scope. So while this will work: 

let greeting = "say Hi";
greeting = "say Hello instead";  

Following statement will return an error: 

let greeting = "say Hi";
let greeting = "say Hello instead"; 
// error: Identifier 'greeting' has already been declared
 

However, if the same variable is defined in different scopes, there will be no error:

 let greeting = "say Hi";
 if (true) {
    let greeting = "say Hello instead";
    console.log(greeting); // "say Hello instead"
 }
console.log(greeting); // "say Hi"

 

 

Const

Variables declared with the const maintain constant values. const declarations share some similarities with let declarations.

 

const declarations are block scoped

Like let declarations, const declarations can only be accessed within the block they were declared.

 

const cannot be updated or re-declared

This means that the value of a variable declared with const remains the same within its scope. It cannot be updated or re-declared. So if we declare a variable with const, we can neither do this:

const greeting = "say Hi";
greeting = "say Hello instead";// error: Assignment to constant variable. 

The following will cause the error as well

const greeting = "say Hi";
const greeting = "say Hello instead";// error: Identifier 'greeting' has already been declared

Every const declaration, therefore, must be initialized at the time of declaration.

This behavior is somehow different when it comes to objects declared with const. While a const object cannot be updated, the properties of this objects can be updated. Therefore, if we declare a const object as this:

const greeting = {
message: "say Hi",
    times: 4
}

while we cannot do this:

 
greeting = {
words: "Hello",
    number: "five"
} // error:  Assignment to constant variable.

we can do this:

greeting.message = "say Hello instead";

This will update the value of greeting.message without returning errors.

 

 

JavaScript Object

JavaScript Primitive type:

string
number
boolean
null
undefined
symbol
bigint

 

A JavaScript object is a collection of named values

It is a common practice to declare objects with the const keyword.

const person = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};
console(person.firstName + " " + person.lastName); //print John Doe

 

 We have an object person, we get his name from the UI and he owns several cars, how do we make JSON string for this.

How to pass JavaScript Object to extension with array

const cars = ["Saab", "Volvo", "BMW", "AnotherOne", "Something"];
var vals=[];
for(var i=0;i<cars.length;i++){
   vals.push(cars[i]);
}  
     const person = {
        Name: 'John',
        Age: 25,
        Married: false,
        Cars : vals
    };  
var myJsonString = JSON.stringify(person);
const myobjectBack = JSON.parse(myJsonString);
console.log(myobjectBack);

The output will be :

Object
Age: 25
Cars: (5) ["Saab", "Volvo", "BMW", "AnotherOne", "Something"]
Married: false
Name: "John"
__proto__: Object

And if we pass the data to extension, we can find the value of 

TcHmi.Symbol.writeEx('%s%ServerExtensionCSharpEmpty1.WriteValueAdd%/s%', myJsonString,
function(data) {
  if (data.error === TcHmi.Errors.NONE) {
    console.log('Write OK');
  } else {
    console.log('Write NOT OK');
  }
});
command.WriteValue = {"Name":"John","Age":25,"Married":false,"Cars":["Saab","Volvo","BMW","AnotherOne","Something"]}

 

JavaScript typeof

In JavaScript there are 5 different data types that can contain values:

string
number
boolean
object
function

There are 6 types of objects:

Object
Date
Array
String
Number
Boolean

And 2 data types that cannot contain values:

null
undefined
typeof "John"                 // Returns "string"
typeof 3.14                   // Returns "number"
typeof NaN                    // Returns "number"
typeof false                  // Returns "boolean"
typeof [1,2,3,4]              // Returns "object"
typeof {name:'John', age:34}  // Returns "object"
typeof new Date()             // Returns "object"
typeof function () {}         // Returns "function"
typeof myCar                  // Returns "undefined" *
typeof null                   // Returns "object"

JavaScript Array function

concat()    Joins arrays and returns an array with the joined arrays

constructor Returns the function that created the Array object’s prototype

copyWithin() Copies array elements within the array, to and from specified positions

entries()   Returns a key/value pair Array Iteration Object

every() Checks if every element in an array pass a test

fill()    Fill the elements in an array with a static value

filter() Creates a new array with every element in an array that pass a test

find() Returns the value of the first element in an array that pass a test

findIndex() Returns the index of the first element in an array that pass a test

forEach() Calls a function for each array element

from() Creates an array from an object

includes() Check if an array contains the specified element

indexOf() Search the array for an element and returns its position

isArray() Checks whether an object is an array

join() Joins all elements of an array into a string

keys() Returns a Array Iteration Object, containing the keys of the original array

lastIndexOf() Search the array for an element, starting at the end, and returns its position

length Sets or returns the number of elements in an array

map() Creates a new array with the result of calling a function for each array element

pop() Removes the last element of an array, and returns that element

prototype Allows you to add properties and methods to an Array object

push() Adds new elements to the end of an array, and returns the new length

reduce() Reduce the values of an array to a single value (going left-to-right)

reduceRight() Reduce the values of an array to a single value (going right-to-left)

reverse() Reverses the order of the elements in an array

shift() Removes the first element of an array, and returns that element

slice() Selects a part of an array, and returns the new array

some() Checks if any of the elements in an array pass a test

sort() Sorts the elements of an array

splice() Adds/Removes elements from an array

toString() Converts an array to a string, and returns the result

unshift() Adds new elements to the beginning of an array, and returns the new length

valueOf() Returns the primitive value of an array

Maps

A Map holds key-value pairs where the keys can be any datatype. A Map remembers the original insertion order of the keys. A Map has a property that represents the size of the map.

const fruits = new Map([
  ["apples", 500],
  ["bananas", 300],
  ["oranges", 200]
]);
console.log(fruits.get("apples")); // 500

JSON

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

JSON data is written as name/value pairs.

Understanding JSON Schema: https://json-schema.org/understanding-json-schema/index.html

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

JSON Example:

{"name":"John"}

 A common use of JSON is to exchange data to/from a web server. When sending data to a web server, the data has to be a string. Convert a JavaScript object into a string with JSON.stringify().

const obj = {name: "John", age: 30, city: "New York"};

 

JSON.stringify() 

Use the JavaScript function JSON.stringify() to convert it into a string. An object is converted to string before transferring to server or vice versa.

const myJSON = JSON.stringify(obj);
console.log(myJSON);  //output {"name":"John","age":30,"city":"New York"}

 

JSON.parse() 

JSON is converted to object and then we do analysis with the object.

let anotherObject = JSON.parse(myJSON);
var combobox = TcHmi.Controls.get("TcHmiNameBox");
combobox .setText(anotherObject.name);
console.log(`anotherObject.name = ${anotherObject.name}`);

anotherObject  and obj has the same fields and value.

Convert an JSON to string by https://tools.knowledgewalls.com/json-to-string

 

Reading JSON Array Data from PLC

We have an array of objects which has been declared in PLC

AS JSON:

 {
  "Test1": "100",
  "Test2": 1
 }

The symbol in PLC can be :

 

Reading an Item at any index :

Reading the first array entry , it will get as an Object of type JSON

data.value will be the JSON object with 2 fields Test1 and Test2.

"%s%PLC1.MAIN.myArray_HMI[0]%/s%"
Reading all data %s%PLC1.MAIN.myArray_HMI%/s%

Reading all data at a time from PLC:

TcHmi.Symbol.readEx2('%s%PLC1.MAIN.myArray_HMI%/s%', function (data){

data.value is as array of object of type JSON object, how many entry we have,  data.value.length will give it.

 

Writing single object to a particular index in PLC:

Both JSON string and object works when we update an element of an array. We can pass the data as object or JSON string. The result is same which is indicated by myJSONString/myObject in the following code.

var myJSONString = JSON.stringify(myObject);
TcHmi.Symbol.writeEx('%s%PLC1.MAIN.myArray_HMI[0]%/s%',  myJSONString/myObject, function (data) {
 

 

Writing all objects to PLC:

Full array can be saved as array of objects or convert array to JSON then pass the data to writeEx as shown in the following code.

TcHmi.Symbol.writeEx('%s%PLC1.MAIN.myArray_HMI%/s%', asArray);
TcHmi.Symbol.writeEx('%s%PLC1.MAIN.myArray_HMI%/s%', JSON.stringify(asArray));

 

Tips

01: SyntaxError: Unexpected token o in the JSON at position 1

const cars = ["Saab", "Volvo", "BMW"];
let myvar = JSON.parse(cars);

The JSON.parse() method parses a string and returns a JavaScript object, but we are trying to parse an array of string.

02: An error occurred when we use to parse array

The JSON.parse() method parses a string and returns a JavaScript object, but we are trying to parse an array

03: 

 An uncaught exception occurred in the processing of a JavaScript action

 Exception: SyntaxError: Unexpected identifier

04: 

Right way to use the JSON and array

05:  Declare variable in right place when we write the code to avoid surprise such as variables are not updated properly.

Lets see the following code:

const cars = ["Saab", "Volvo", "BMW", "SKODA", "OPEL"];
var vals = [];
var newObject = {
carname: '',
id: 0
};
for (var i = 0; i < cars.length; i++) {
if (cars[i] != undefined && cars[i].length > 0)
newObject.carname = cars[i];
newObject.id = i + 1;
vals.push(newObject);
}
let myString = JSON.stringify(vals);
console.log(`myString = ${myString}`);

 //output as follows and we see carname is OPEL and id is 5

myString = [{“carname”:”OPEL”,”id”:5},{“carname”:”OPEL”,”id”:5},{“carname”:”OPEL”,”id”:5},{“carname”:”OPEL”,”id”:5},{“carname”:”OPEL”,”id”:5}]

 

Solution to this problem is we declare in the following way:

const cars = ["Saab", "Volvo", "BMW", "SKODA", "OPEL"];
var vals = [];
for (var i = 0; i < cars.length; i++) {
  var newObject = {
carname: '',
id: 0
};  
if (cars[i] != undefined && cars[i].length > 0)
newObject.carname = cars[i];
newObject.id = i + 1;
vals.push(newObject);
}
let myString = JSON.stringify(vals);
console.log(`myString = ${myString}`); // This will give the correct output

06: 

When we call an asynchronous function call from the TwinCAT HMI framework then the result comes by callback. In the following image we see that when we tried to read data we got an error.

data.details.code = 3005 when has been described in the page https://infosys.beckhoff.com/english.php?content=../content/1033/te2000_tc3_hmi_engineering/3758305291.html&id=

The reason is data.details.errors[0].reason.

07: 

When  we use JSON.perse() and when stringfy()

Normally when we project data inside our software then it is better to have objects. When we transfer data to remote server then we transfer as string.

When data arrives to our software we need to use JSON.perse()  to get objects and before transferring data we use stringfy(). We can’t transfer objects but string. Consult or check if the API provides  string or object.

 

By the following sample, we are converting myObject to JSON string then writing to writeEx method.

var myJSONString = JSON.stringify(myObject);
TcHmi.Symbol.writeEx('%s%ServerExtensionCSharpEmpty1.WriteValueAdd%/s%', myJSONString, function (data) {

Following example, expect that data3 will arrive as string and then we can convert to object for processing.

TcHmi.Symbol.readEx2('%s%PLC1.MAIN.collectionGroups_HMI%/s%', function (data3) {
if (data3.error === TcHmi.Errors.NONE) {

 

PLC variable is not seen in HMI though it has been mapped OK

08: 

When  we change the data in the PLC program then we need to map these again. Some data mapping goes wrong and the variables are not seen properly. For example, we have a small program as shown below:

//we have an array with index 0 and and last index 3, having 4 elements
PROGRAM MAIN
VAR
myArray_HMI : ARRAY [0..MAX_AMOUNT_INDEX] OF MyArray_struct; 
END_VAR
VAR CONSTANT
MAX_AMOUNT_INDEX: INT := 3;
END_VAR
//Now we redeclare the array  to 10,
so for HMI will have an mismatch and HMI will not able to read these. 
MAX_AMOUNT_INDEX : INT := 10;

Solution: We should check the mapped variables and remapped those, done!

 

09:  

Type mismatch (3005), Invalid type at ´DataField´ : ´Expected string, got int.

The error occurs when we have a type mismatch. For example, we have the following data in PLC.

TYPE MyData_struct :
STRUCT
ComboBox : INT;
END_STRUCT
END_TYPE
//In JavaScript, 
     const myObject = {
        ComboBox : ´
    };    
var myJSONString = JSON.stringify(myObject);
TcHmi.Symbol.writeEx('%s%PLC1.MAIN.myArray_HMI%/s%', myJSONString, function (data) {
    if (data.error === TcHmi.Errors.NONE) {
        // Handle success... 
    } else {
        //We got an error 3005
    }
});

The following error code is shown when code is 3005

 

10:  JavaScript – Cannot set property of undefined.

The error occurs when we have a type mismatch. By mistake we are treating myObject as an array though we should use myNewArray .

https://stackoverflow.com/questions/7479520/javascript-cannot-set-property-of-undefined

Let’s show the occurrence by an example.

var myNewArray = [];
const myObject = {
myDay : 0,
myName : ' John'
};
for(var i = 0; i < 10; i++) {
myObject [l].myDay  = anotherArray[l].myDay; //Causes Cannot set property of undefined.
myObject [l].myName = anotherArray[l].myName;//Causes Cannot set property of undefined.
}

 

References: