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.

 

Figure 1: Usages of JavaScript function in TwinCAT environment

When we use JavaScript in the TwinCAT environment, we can see the console output in the following way.

=> Press on the live view

=> Press on the gear icon

=> Press on the console tab, now we can see the output of our program (if we have used the console.log function)

Note: If we use the browser for example Chrome (press F12) or find the developers tool in the browser menu

Figure 2: Shows how to display the console out with the TwinCAT tools (F12 or developer tools with Chrome)

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();

Self calling functions:

Function expressions can be made “self-invoking”. A self-invoking expression is invoked (started) automatically, without being called. Function expressions will execute automatically if the expression is followed by (). You cannot self-invoke a function declaration. You have to add parentheses around the function to indicate that it is a function expression:

(function () {
  let x = "Hello!!";  // I will invoke myself
  console.log('Ha ha calling myself');
})();

Using anonymous functions as arguments:

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

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

Or if we format the above code it will be like the following. If we see a bit carefully the formatted code below, we have first one anonymous function and then a comma, and then 1000

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

 

setTimeout takes 2 parameters, the first one is an anonymous function, and the 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. More has been described at https://infosys.beckhoff.com/english.php?content=../content/1033/te2000_tc3_hmi_engineering/4702049035.html&id=

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 called 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:

Create a code behind JavaScript file. We add two functions, one for reading and another for writing data to the PLC using promise. Note that when there is no error then we call resolve and when there is an error we call reject and pass a custom JavaScript object. See errorData object.

function ReadPLCVariable(symbolString) {
    return new Promise((resolve, reject) => {
        var symbol = new TcHmi.Symbol(symbolString);
        symbol.readEx(function (data) {
            if (data.error === TcHmi.Errors.NONE) {
                resolve(data.value);
            } else {
            const errorData = {
                ErrorCode: data.error,
                ErrorText: `Error in ReadPLCVariable symbolString = ${symbolString}`
                };
                reject(errorData);
            }
        });
    });
}
function WritePLCVariable(symbolParameter, dataParameter) {
    return new Promise((resolve, reject) => {
        TcHmi.Symbol.writeEx(symbolParameter, dataParameter, function (data) {
            if (data.error === TcHmi.Errors.NONE) {
                resolve(data.error);
            } else {
            const errorData = {
                ErrorCode: data.error,
                ErrorText: `Error in WritePLCVariable, symbolParameter = ${symbolParameter}`
                };
                reject(errorData);
            }
        });
    });
}

We shall use those functions from an action and condition editor that is from an embedded JavaScript code.

async function asyncFunctionCall() {
try {     const doorAtHome = await ReadPLCVariable('%s%PLC1.MAIN.doorAtHome%/s%');     const motorStatus = await ReadPLCVariable('%s%PLC1.MAIN.motorStatus%/s%');     if(doorAtHome == true && motorStatus == true){     await WritePLCVariable('%s%PLC1.MAIN.motorOutPut%/s%', true);     } }
catch(error) {     console.log(`ErrorCode = ${error.ErrorCode}`);     console.log(`ErrorText = ${error.ErrorText}`);   } } asyncFunctionCall();

If all are OK then motorOutPut will be written to true and there will be no console output. But if we make some mistake for example we write mistake doorAtHome to doorAtHome1  then we get the following error code in the console window.

ErrorCode = 3005
ErrorText = Error in ReadPLCVariable symbolString = %s%PLC1.MAIN.doorAtHome1%/s%

So we know when and what error occurred with much nicer code! 

In this way, we can avoid the Callback Hell see more at http://callbackhell.com/

Download the test sample TcHmiJSBasicPromise.zip

Async function may not have async function call

We can use the async function as the usual JavaScript function. It may not have any asynchronous calls inside. Let’s see the following example.

async function asyncFunctionCall2(mypar) {
try {  
console.log('Got mypar as ' + mypar);   // Got mypar as Hello Hemelix            
}
catch(error) {
    console.log(`Some error!`);
  }
}
let myVariable = 'Hello Hemelix';
asyncFunctionCall2(myVariable);
 

Get value by calling .then method

We can create our own function in the code behind the file to read data. We have a variable in PLC that we shall read and write.

'%s%PLC1.MAIN.startTimer%/s%'
var boolvar = true; // a global variable
function ReadPLCVariable(symbolString) {
    return new Promise((resolve, reject) => {
        var symbol = new TcHmi.Symbol(symbolString);
        symbol.readEx(function (data) {
            if (data.error === TcHmi.Errors.NONE) {
                resolve(data.value);
            } else {
                const errorData = {
                    ErrorCode: data.error,
                    ErrorText: `Error in ReadPLCVariable symbolString = ${symbolString}`
                };
                reject(errorData);
            }
        });
    });
}
function WritePLCVariable(symbolParameter, dataParameter) {
    return new Promise((resolve, reject) => {
        TcHmi.Symbol.writeEx(symbolParameter, dataParameter, function (data) {
            if (data.error === TcHmi.Errors.NONE) {
                resolve(data.error);
            } else {
                const errorData = {
                    ErrorCode: data.error,
                    ErrorText: `Error in WritePLCVariable, symbolParameter = ${symbolParameter}`
                };
                reject(errorData);
            }
        });
    });
}
function myfunctionRead() {
    var myPromise = ReadPLCVariable('%s%PLC1.MAIN.startTimer%/s%');
    myPromise.then(function (result) {
        console.log(` result1 = ${result}`);
    });
}
function myfunctionWrite() {
    boolvar = !boolvar;
    var myPromise1 = WritePLCVariable('%s%PLC1.MAIN.startTimer%/s%', boolvar);
    myPromise1.then(function (result) {
        console.log(` result2 = ${result}`);
    });
}

If we call myfunctionWrite then the boolean variable will be swapping the value in PLC and we can read the data from PLC by the myfunctionRead function. These 2 functions can be called from JavaScript embedded with a button click. 

Handling error:

In the following code we are displaying how to handle error situations.

function myfunctionRead() {
    var myPromise = ReadPLCVariable('%s%PLC1.MAIN.startTimer%/s%');
    myPromise.then(function (result) {
        console.log(` result1 = ${result}`);
    }).catch((error) => {
        console.log(` error.ErrorCode = ${error.ErrorCode}`);
        console.log(` error.ErrorText = ${error.ErrorText}`);
    });
    myPromise.catch((error) => {
        console.log(` error.ErrorCode = ${error.ErrorCode}`);
        console.log(` error.ErrorText = ${error.ErrorText}`);
    });
}

Handling error case when we call write:

function myfunctionWrite() {
    boolvar = !boolvar;
    var myPromise1 = WritePLCVariable('%s%PLC1.MAIN.startTimer%/s%', boolvar);
    myPromise1.then(function (result) {
        console.log(` result2 = ${result}`);
    }).catch((error) => {
        console.log(` error.ErrorCode while writing = ${error.ErrorCode}`);
        console.log(` error.ErrorText while writing = ${error.ErrorText}`);
    });
}

Even we can put other asynchronous code:

function myfunctionRead() {
    var myPromise = ReadPLCVariable('%s%PLC1.MAIN.startTimer%/s%');
    myPromise.then(function (result) {
        console.log(` result1 = ${result}`);
    });
    fetch("https://jsonplaceholder.typicode.com/users/1") //using fetch API
        .then((response) => response.json())
        .then((user) => {
            console.log(user.address);
        });
}

Asynchronous function to synchronous

Call asynchronous function inside TwinCAT function.

See the following code, we have our function and we are calling an async function

            function ShowErrorToUI(Data, Message) {
                console.log(`Data point #1`);
                var BoolData = undefined;
                let myParameterData = false;
                async function asyncMyCall() {
                    try {
                        myParameterData = await ReadPLCVariable('%s%PLC1.MAIN.doorAtHome%/s%');
                        console.log(`myParameterData = ${myParameterData}`);                      
                        BoolData = await ReadPLCVariable('%s%PLC1.MAIN.motorStatus%/s%');
                        console.log(`BoolData = ${BoolData}`);                        
                        console.log(`Data point #2`);
                    }
                    catch (error) {
                        console.log(`error occured = $ {error}`);
                    }
                    console.log(`Data point #3`);
                }
                console.log(`Data point #4`);
                asyncMyCall();
                console.log(`Data point #5`);
            }

The output of the above function

Data point #1
Data point #4
Data point #5
myParameterData = true
BoolData = true
Data point #2
Data point #3

The point I see is that if we want to use manipulated variable by the async function we have to be careful when to use those variables. 

Lets see the following example.

var result = 0;
function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(20);
    }, 2000);
  });
}
async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log('result =' + result);
  return result;
}
var v = asyncCall();
console.log(v);

Output:

"calling"
[object Promise] { ... }
"result =20"

asyncCall print the calling and then return the promise object and finally the result is printed.

More about asynchronous function

undefined or 'undefined'

What are the differences between undefined and ‘undefined’ in JavaScript?

undefined is a primitive value automatically assigned to variables that have just been declared, or to formal arguments for which there are no actual arguments.

‘undefined’ is a string

var a = 'undefined';
if(a == 'undefined') {
console.log('a is undefined');  // a is undefined
}
var a ;
if(a == 'undefined') {
console.log('a is undefined');  // no output as a is not equal to string 'undefined'
}
var a ;
if(a == undefined) {
console.log('a is undefined'); // a is undefined
}

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 can write the above code by formatting it in the following way.

var person = {
firstName: "John",
lastName: "Doe",
age: 50,
eyeColor: "blue"
};
 // returns 50
console.log(person ['age'] ); // 50
console.log(person ['eyeColor'] ); //blue
person.hasAddress = false;  //new property
delete person.eyeColor;  //delete eyeColor property
console.log(person ['eyeColor'] );  //undefined
console.log(person ['hasAddress'] );  //false
for (var key in person){
  console.log(key); // logs keys in person
  console.log(person[key]); // logs values in person
}

 

We can access object property by dot notation or bracket notation. 

 

 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"]}

 

Complex JavaScript Object

var globalArray = [];
var globalObjects = {};
var globalStrokeColor = '#00ff00';
globalObjects['objectID_1'] = {
coordinates: [{
lat: 65.71988818952737,
lng: 26.859262689448833
},
{
lat: 65.72081178499442,
lng: 26.857662854998747
}
],
strokeColor: globalStrokeColor,
faulty: false,
name: "objectID_1"
};
for (var singleObject in globalObjects) {
console.log(globalObjects[singleObject].coordinates[0].lat);  // lat value from inner object
console.log(globalObjects[singleObject].faulty); //faulty property
console.log(globalObjects[singleObject].name); //name property
}

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 Object and Array (both are object)

The Object.assign() static method copies all own enumerable properties from one or more source objects to a 

target object. It returns the modified target object. We can test with the following two sample codes.

var obj = {
  0: 'a',
  1: 'b',
  2: 'c'
};
var arr = ['a','b','c'];
var myobj = Object.assign({}, arr);
console.log(myobj);
console.log(typeof myobj);
console.log(Array.isArray( myobj));
var myarr = Object.assign([], obj);
console.log(myarr);
console.log(typeof myarr);
console.log(Array.isArray( myarr));
The output of this is shown in the following image.
var obj = {
   name: 'Jin Doe',
   age: '18',
   gender: 'female',
   getDetails: function(){
      return `${this.name} is ${this.age} years old`;
   }
};
console.log(typeof obj); //output "object"
var arr = [123, 233, 34, 415, 536, 673];
console.log(typeof arr); //output "object"

Go through keys and values:

for (var key in obj) {
  console.log(key); // logs keys in obj
  console.log(obj[key]); // logs values in obj
}

Some selected array functions:

entries()   Returns a key/value pair Array Iteration Object
forEach() Calls a function for each array element
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
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
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

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"};

 

Object and Array:

The following JS object contains array of object which describes language code.

{
    "languages": 
    [
       {
          "eng": "1"
       },
       {
          "ger": "2"
       }
    ]
}

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

 

var vals = [];
const myData = {
  CurrentTime: 'current time',
  Test1: 'A anme',
  Test2: "2nd str"
};
vals.push(myData);
let valsString = JSON.stringify(vals);
let myDataString = JSON.stringify(myData);
console.log(valsString);
console.log(myDataString);
let anObjectArray = JSON.parse(valsString);
let anObject = JSON.parse(myDataString);
console.log(anObjectArray[0].CurrentTime);
console.log(anObjectArray[0].Test2);
console.log(anObject.Test2);

Output of the above program, note that when we convert array to string that start with ‘[‘. String is used for transferring between client and server. Object is handy to use for example  for updating a control,

myControl.setText(anObject.Test2);

//Output

 '[{"CurrentTime":"current time","Test1":"A anme","Test2":"2nd str"}]'
 '{"CurrentTime":"current time","Test1":"A anme","Test2":"2nd str"}'
 "current time"
 "2nd str"
 "2nd str"

 

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_List[0]%/s%"
Reading all data %s%PLC1.MAIN.myArray_List%/s%

Reading all data at a time from PLC:

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

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

 

Writing a single object to a particular index in PLC:

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

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

Writing all objects to PLC:

Download sample where we can delete, insert, etc in GridView (Array, JSON, etc)

A full array can be saved as an array of objects or converted array to JSON then pass the data to writeEx as shown in the following code.

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

JavaScript Object and JSON

We don’t need a quotation for the JS object (with or without does not make any difference).

const jsonData1 = {
"name": "John", "age": 22 };
const jsonData2 = {
name: "John", age: 22 };
const obj1 = JSON.stringify(jsonData1);
console.log(obj1);
const obj2 = JSON.stringify(jsonData2);
console.log(obj2);
console.log(typeof(jsonData1)); //"object"
console.log(typeof(jsonData1)); //"object"
console.log(typeof(obj1)); //"String"
console.log(typeof(obj2)); //"String"

 

 

 



var globalArray = [];
var globalObjects = {};
var globalStrokeColor = '#00ff00';
globalObjects['objectID_1'] = {
coordinates: [{
lat: 65.71988818952737,
lng: 26.859262689448833
},
{
lat: 65.72081178499442,
lng: 26.857662854998747
}
],
strokeColor: globalStrokeColor,
faulty: false,
name: "objectID_1"
};
globalObjects['objectID_2'] = {
coordinates: [{
lat: 65.71988818952737,
lng: 26.859262689448833
},
{
lat: 65.72081178499442,
lng: 26.857662854998747
}
],
strokeColor: globalStrokeColor,
faulty: false,
name: "objectID_2"
};
for (const [key, value] of Object.entries(globalObjects)) {
  //console.log(`${key}: ${value}`);
  console.log(JSON.stringify(value));
}
//console.log(Object.keys(globalObjects));


Debug JS Code in Visual Studio Code

JSON Schema

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_List%/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_List : 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.

You have the following code in PLC, you can access it by ‘%s%PLC1.MAIN.bigData.0%/s%’ or ‘%s%PLC1.MAIN.bigData[0]%/s%’ depending on how we have mapped the data. If we have mapped the array from the root level then we use 2nd form to access individual arrays otherwise we get 3005

bigData : ARRAY[0..4] OF deviceProperty;

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_List%/s%', myJSONString, function (data) {
    if (data.error === TcHmi.Errors.NONE) {
        // Handle success... 
    } else {
        //We got an error 3005, also if you write out of range data you get this
    }
});

The following error code is shown when the code is 3005 (type mismatch and Range mismatch in the Array). Another reason could be that if you have an array of objects and have changed the object, then the mapping goes invalid. You refresh or remap the array. You have 90 item in an array and later you changed to 80, causes 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.
}

 

11:  JavaScript – Resolve calls OK but the catch block is executed (the error is undefined)

This happens, in that case, if we have done a mistake after calling the promise function, the following function will generate such an error.

async function asyncFunctionCall() {
try {
    var all = await ReadPLCVariable('%s%PLC1.MAIN.ArrayGate%/s%');
    cosole.log(all);  //<= this can cause such, it should be console but we have written cosole.log
    } catch(errorObject) {
    console.log(`ErrorCode = ${errorObject.ErrorCode}`);
    console.log(`ErrorText = ${errorObject.ErrorText}`);
  }    
}
asyncFunctionCall();

 

 

12:  PLC array size change and HMI needs remapping

We have an array

myArray : ARRAY [0..9] OF INT

Now it has redefined

myArray : ARRAY [0..5] OF INT

In this case, myArray will not be available if we don’t remap myArray

 

13:  When we use then and async

If we don’t use async  then we need to  use then see the sample at section Get value by calling .then method

In the following code we use async keyword inside another function and we are not using .then()

function RefreshGridViewWithPLC(GridControl) {
var readData;
async function asyncCall() {
readData = await ReadPlcData('%s%PLC1.MAIN.bigData%/s%');
gtotalPLCLength = readData.length;
var listBoxData = [];
for (var i = 0; i < gtotalPLCLength; i++) {
var emptyObject = {
boolData: false,
};
emptyObject.boolData = readData[i].boolData;
listBoxData.push(emptyObject);
}
GridControl.setSrcData(listBoxData);
}
try {
asyncCall();
} catch (error) {
console.log(`ErrorCode Calling RefreshGridViewWithPLC = ${error}`);
}
}

 

References:

Download the sample from the link given above.

See next how to use JavaScript at https://www.hemelix.com/scada-hmi/twincat-hmi/twincat-javascript-function-and-code-behind/

Ask questions related to Hemelix sample code and design at Google group https://groups.google.com/g/hemelix