TwinCAT HMI User Control inside another user control

User Control

 

YouTube Video is at the end of Document

We have learned the user control in the previous tutorial (https://www.hemelix.com/scada-hmi/beckhoff-hmi/beckhoff-hmi-user-control/) and seen how we can create reusable UI components. It is like a class in a high-level programming language where we can create instances of classes as needed. We can include the class from another class and so on. In this way, we can build user control which contains another user control developed by ourselves.

CompoundUserControl_Parameter.zip (PLC data are passed as parameters from one level to another level )

 

CompoundUserControl_Subscription.zip (PLC data are read by subscription and then those fields are updated by JavaScript code )

 

The scenario is like this: We have a rotation sensor that will be used to sense the rotation speed of a motor (fictitious case). The motor is equipped with 2 wheels (each can have a different speed due to the gearbox or some other reasons). Each side will have rotation sensors. Since it is a fictitious case, our yard gate needs 2 motors. In the yard, we have 2 gates. We shall develop an HMI for this. The following image is showing what the HMI is doing. We have a total of 4 motors, 8 sensors, and 2 gates, each has been designed as a user control. The rotation speed is simulated by a timer. If we change the timer interval then the rotation speed is changed. The default interval is 500 MS. If we click on the speed then it shows a popup by which we can change the speed (how the popup works, please take a look at https://www.hemelix.com/scada-hmi/beckhoff-hmi/more-on-javascript-and-usercontrol/).  The following image show when the HMI is running. We say Gate and Motor are compound user control (contain other user control), Rotation.usercontrol is the basic user control.

We have designed the UI in three different samples:

Design 1: In this sample, we pass the PLC variables for linking (no popup, meaning you can’t change the speed) 

Design 2: In this sample, we use the subscription mechanism in HMI (you need to take a look at https://www.hemelix.com/scada-hmi/beckhoff-hmi/mapping-and-subscription-using-js/ before studying this example.)

Design 3: Changing parameters (speed of the motor by changing the timer interval), We shall describe the YouTube Video by using this sample.

 

Figure 1:  showing the final HMI, when the alarm is active then we change the motor background color and text color

Design 1: Parameter passing

In this design, we shall pass parameters from one level to another level as was done in the previous chapter. So this is not new and we have done the practice at https://www.hemelix.com/scada-hmi/beckhoff-hmi/beckhoff-hmi-user-control/

The sample can be downloaded from the above link and we shall not discuss it further. If you have any questions related to this, you could post a question to our google group.

Design 2: Subscription based

In this sample, we shall show how to fill the HMI field based on subscription. We shall not touch the upper gate but we shall change the lower gate based on subscription.

Figure 2:  Showing each user control level.

Code snippet showing how to use subscription, the advantage of this is that we don’t link each variable, as you have seen it is confusing to use indices and so on while we are linking.


// Keep these lines for a best effort IntelliSense of Visual Studio 2017 and higher.
/// <reference path="../../Packages/Beckhoff.TwinCAT.HMI.Framework.12.742.5/runtimes/native1.12-tchmi/TcHmi.d.ts" />
(function (/** @type {globalThis.TcHmi} */ TcHmi) {
    var Functions;
    (function (/** @type {globalThis.TcHmi.Functions} */ Functions) {
        var CompoundUserControl;
        (function (CompoundUserControl) {
            function ManageVariableByJavaScript(ControlName, GateName) {  //'Gate_LowerOne' 'Gate 2'  Use identifier not the control name
                
                if (TcHmi.Server.isWebsocketReady()) {                    
                    var myControl = TcHmi.Controls.get(ControlName.concat('.TcHmi_Controls_Beckhoff_TcHmiTextblock_GateControlName')); //Note actual field
                    if (myControl) {                     
                        myControl.setText(GateName);                        
                        var leftMotor = TcHmi.Controls.get(ControlName.concat('.Motor_LeftMotor'));
                        var rightMotor = TcHmi.Controls.get(ControlName.concat('.Motor_RightMotor'));
                        var sensorOne = TcHmi.Controls.get(ControlName.concat('.Motor_LeftMotor').concat('.Rotation__LeftSide').concat('.TcHmi_Controls_Beckhoff_TcHmiTextblock_MotorSpeed'));
                        var sensorTwo = TcHmi.Controls.get(ControlName.concat('.Motor_LeftMotor').concat('.Rotation__RightSide').concat('.TcHmi_Controls_Beckhoff_TcHmiTextblock_MotorSpeed'));
                        var sensorThree = TcHmi.Controls.get(ControlName.concat('.Motor_RightMotor').concat('.Rotation__LeftSide').concat('.TcHmi_Controls_Beckhoff_TcHmiTextblock_MotorSpeed'));
                        var sensorFour = TcHmi.Controls.get(ControlName.concat('.Motor_RightMotor').concat('.Rotation__RightSide').concat('.TcHmi_Controls_Beckhoff_TcHmiTextblock_MotorSpeed'));
                        var commands = [
                            {
                                'symbol': 'PLC1.MAIN.ArrayGate.1.ArrayMotorControl.0.startMotor'                                
                            },
                            {
                                'symbol': 'PLC1.MAIN.ArrayGate.1.ArrayMotorControl.1.startMotor'
                            },
                            {
                                'symbol': 'PLC1.MAIN.ArrayGate.1.ArrayMotorControl.0.ArrayROTATIONSENSOR.0.readData'
                            },
                            {
                                'symbol': 'PLC1.MAIN.ArrayGate.1.ArrayMotorControl.0.ArrayROTATIONSENSOR.1.readData'
                            },
                            {
                                'symbol': 'PLC1.MAIN.ArrayGate.1.ArrayMotorControl.1.ArrayROTATIONSENSOR.0.readData'
                            },
                            {
                                'symbol': 'PLC1.MAIN.ArrayGate.1.ArrayMotorControl.1.ArrayROTATIONSENSOR.1.readData'
                            }
                        ];
                        TcHmi.Server.subscribe(commands, 500, function (data) {
                            if (data.error !== TcHmi.Errors.NONE) {
                                // Handle TcHmi.Server class level error here.
                                return;
                            }
                            var response = data.response;
                            if (!response || response.error !== undefined) {
                                // Handle TwinCAT HMI Server response level error here.
                                return;
                            }
                            var commands = response.commands;
                            if (commands === undefined) {
                                return;
                            }
                            for (var i = 0, ii = commands.length; i < ii; i++) {
                                var command = commands[i];
                                if (command === undefined) {
                                    return;
                                }
                                if (command.error !== undefined) {
                                    // Handle TwinCAT HMI Server command level error here.
                                    return;
                                }
                                if (commands[i].symbol.localeCompare('PLC1.MAIN.ArrayGate.1.ArrayMotorControl.0.ArrayROTATIONSENSOR.0.readData') == 0) {
                                    if (sensorOne) {
                                        var num = command.readValue;
                                        sensorOne.setText(num.toFixed(2));
                                    }                                    
                                }
                                if (commands[i].symbol.localeCompare('PLC1.MAIN.ArrayGate.1.ArrayMotorControl.0.ArrayROTATIONSENSOR.1.readData') == 0) {
                                    if (sensorTwo) {
                                        var num = command.readValue;
                                        sensorTwo.setText(num.toFixed(2));
                                    }
                                }
                                if (commands[i].symbol.localeCompare('PLC1.MAIN.ArrayGate.1.ArrayMotorControl.1.ArrayROTATIONSENSOR.0.readData') == 0) {
                                    if (sensorThree) {
                                        var num = command.readValue;
                                        sensorThree.setText(num.toFixed(2));
                                    }
                                }
                                if (commands[i].symbol.localeCompare('PLC1.MAIN.ArrayGate.1.ArrayMotorControl.1.ArrayROTATIONSENSOR.1.readData') == 0) {
                                    if (sensorFour) {
                                        var num = command.readValue;
                                        sensorFour.setText(num.toFixed(2));
                                    }
                                }
                            } //for
                        }); // subscribe
                } //   if (myControl) 
                }//Web socket ready
            } //function ManageVariableByJavaScript(ControlName) {
            CompoundUserControl.ManageVariableByJavaScript = ManageVariableByJavaScript;
        })(CompoundUserControl = Functions.CompoundUserControl || (Functions.CompoundUserControl = {}));
        Functions.registerFunctionEx('ManageVariableByJavaScript', 'TcHmi.Functions.CompoundUserControl', CompoundUserControl.ManageVariableByJavaScript);
    })(Functions = TcHmi.Functions || (TcHmi.Functions = {}));
})(TcHmi);

 

Configure the lower gate and call the subscription method, subscription method has 2 parameters ControlName and GateName, both are string types. Once we have pressed on the StartMotor button then we see the motor speed changing (simulated in PLC with timer). The method calling mechanism is shown in the following picture (we shall explain this in a video tutorial)

Figure 3:  Shows how to control the Java Script method for subscription (how subscription works see the JavaScript chapter)

Design 3: Changing parameter of PLC program by Popup

This is example 3 based on the same example. Here we shall click on the rotation sensor data and change the timer interval for rotation speed simulation purposes. It means, by default the sensor is read by 500 MS interval. We shall change the time interval of the PLC when the user clicks on the rotation value. 

We have considered only the lower part. If we press on a rotation value it will give an option to change the timer value in milliseconds so the data will be generated with that interval.

We have a button StartAllMotor and StopAllMotor, basically, these activate the timer and stop the timer for simulation (please see the PLC code in the zip file).

Figure 4:  Display the gate id, motor id and the sensor index, we can detect which sensor has been pressed.

Figure 5:  Showing all the user controls ( basic + compound user control)

How to design the HMI:

We need a HMI project, where we show the HMI (we shall focus on this part in this tutorial, see https://www.hemelix.com/scada-hmi/beckhoff-hmi/my-first-twincat-hmi/ for starting HMI)

We need a PLC project, we can copy paste the PLC code from the zip file (see how to start PLC program https://www.hemelix.com/automation/first-plc-program/)

=> Start a Beckhoff HMI project by

=> Design 4 user controls (Rotation, Motor,  GateControl and  TimerIntervalSetting)

=> Rotation.usercontrol (see user control at: https://www.hemelix.com/scada-hmi/beckhoff-hmi/beckhoff-hmi-user-control/)

Basic user control implements onPressed (shows the popup) and Custom event (for alarm to change text color)

 

Figure 6:  Design of the user control (Rotation.usercontrol) and it’s parameters

=> Motor.usercontrol

Motor user control contains 2 rotation user control and an motor image. A Custom event is implemented to change the background of motor to red when the alarm is active.

Figure 7:  Design of the compound user control (Motor.usercontrol) and it’s parameters

=> GateControl.usercontrol

Gate user control contains 2 motor user control, gate name and an gate image. This is shown in the following

Figure 8:  Design of the Gate user control and it parameters. Gate user controls are placed on the Desktop and configured with the parameters

=> TimerIntervalSetting.usercontrol

Basic user control that is displayed when user press on the rotation value. It shows the gate name and the motor and sensor indexes and shown in figure 4. For more about pop up take a look at https://www.hemelix.com/scada-hmi/beckhoff-hmi/more-on-javascript-and-usercontrol/

Figure 9:  Design of the popup user control, this is displayed when user press on the rotation value. If we change the value then timer interval is changed in the PLC

If you see the SettingPopup method, you notice the following:

 function SettingsPopup(ParentControl, UserControl, WhereToInsert, GateName, MotorName, SensorName, Interval) {
 ...
 ...
 var myControl = TcHmi.Controls.get(ParentControl);  //Where we are using the control
 if (myControl) {
 popupParametersControl['data-tchmi-headernamepopup'] = GateName.concat('.').concat(MotorName).concat('.').concat(SensorName);

We are building the popup header  by using concat method. Also notice that the gate identifier and name  is ‘Gate_UpperOne’

=> Run the PLC program

=> Press on the StartAllMotor to simulate the sensor, there should be the same rotation values for all sensors.

=> If we press on the rotation value, it will pop up a dialog where we can change the 500 MS to something else. 

=> If we change the value for example to 400 then the speed will be higher.

=> Download this source code (CompoundUserControl_Popup.zip) and check with the YouTube video.

=> If you think it is useful, you could share it to help us.

 

Download the sample from the link given above.

Next, let’s try to understand what is a view at https://www.hemelix.com/scada-hmi/beckhoff-hmi/beckhoff-hmi-view/

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