HMI User Management
The management of users on any software system is critical to the security of the system and the organization. Proper procedures should be in place within the organization to identify the proper permissions each new user should receive. Each person or user may not have equal rights, for example, an expert-level user who knows how the system works as compared to an entry-level worker does not know. So there might be potential risks of misusing the system. This section just describes different features with samples for the TwinCAT environment.
Login page step by step
=> Add couple of text blocks to the HMI
=> Configure Text property of 2nd text block with the following code
TcHmi.Server.getCurrentUser()
Figure 01: Displaying the current HMI user (session specific)
=> Publish and see the simple HMI
=> Check if it ask password to access (If it asks password, you can use the same password when you publish the content)
=> Who is the current user __SystemGuest or __SystemAdministrator
=> Login to the HMI Server, where we are publishing our application (without real HMI server, we can’t complete the test)
=> Check http://127.0.0.1:1010/Config/TcHmiSrv in the server computer for controlling the authentication required
Figure 02: Controlling the authentication required process in HMI server
=> Authentication required
=> None, password is not required (login user is __SystemGuest)
=> Always authenticate, it will ask password
=> You may need to restart the HMI server application to take change in effect (if you had old app published)
Default User
=> We may need to set a user as default, For example, a viewer, whenever the system starts this user will be auto-login
=> We need to understand what is default user and what he can do
=> What kind of users we can have
=> In which group, each user belongs
Figure 03: Menus for creating users and groups
=> We create a user by clicking on <create new user> name it as viewer, password viewer
=> We select the Auto-Login feature checked, when ever the system starts this user will be auto logged in
=> We can use Auto-Logout period very long time (for example 30 days)
=> We can create as many Users as needed in the HMI configuration which will be available when we tray to login
Figure 04: Default user ‘Viewer’ created with Auto logout 30 days
System users and system user groups
The following users and user groups exist as standard in the system and cannot be deleted:
Custom login page
By default, the template creates user such as __SystemGuest (if we don’t have Auto-Login user)
=> Add a custom login page as described in the Beckhoff documentation as shown in the following image.
Figure 05: Add a new item to the project in visual studio
Figure 06: Select the loginPage1.html
Figure 07: Check in the server side if the selection is OK
Figure 08: Login to the HMI (same password used when we publish the content)
Authentication can be kept OFF (username and password will not be asked) by using the configuration of the HMI Server, Authentication required can be set to Always authenticate (always ask the password), or None (password is not asked at all). The configuration page is shown in the following screenshot.
Authentication required -> None, it will not ask the password and the login user will be _SystemGuest
Login page will bring the login page and gives options to user for login dialog -> the user will be current user
Logout -> log out the current user and the current user again _SystemGuest
Figure 09: Always authenticate option asks password and None does not ask it as Figure 02
Now we can add a Login button and Logout button to our Desktop view
=> We call GoToLoginPage method when user click on Login button
=> We call the following JavaScript code when Logout button is pressed
TcHmi.Server.logoutEx({ timeout: 2000 }, function(data) {
console.log('User logout not successful.');
});
Test Application
In the test application we will do the following:
=>We shall list all groups in the system
=>We show in a list how many users are included in the group
=>We shall list all users in the system, individual property can be seen as well
=>New Users can be added, updated, and deleted in the system
=>Custome Login by using user name and password
=>Button active or inactive based on the user’s right
We need to publish the application to see all the functionality (Live view in Visual Studio is not enough)
Figure 10: Main Desktop view of the test application
ListGroups:
List all groups available in the system, following code are necessary for getting a user group list. Please note that we are using async function which are implemented in the CodeBehindJs1.js file
console.log('Calling listGroupsAll'); async function listGroupsAll() { try { var textBlockControl = TcHmi.Controls.get('TcHmiDatagrid_GroupLists'); const result = await listGroups(); var allName = []; for (const [key, value] of Object.entries(result)) { var item = { GroupName: key }; allName.push(item); } textBlockControl.setSrcData(allName); } catch(error) { console.log(`error occured in listGroupsAll = ${error}`); } } listGroupsAll();
If we press on a group item, it will display the users in the group, following code are used when onSelectedItemChanged event. Please note that we are using async function which are implemented in the CodeBehindJs1.js file.
async function SelectItemForGroup() { try { var gridControl = TcHmi.Controls.get('TcHmiDatagrid_GroupLists'); var gridData = gridControl.getSrcData(); var selectedValue = gridControl.getSelectedRowValue(); var properties = await listUsersInGroup(selectedValue.GroupName); var infoTextBlock = TcHmi.Controls.get('TcHmiTextblock_GroupDetails'); var text = ''; for (const [key, value] of Object.entries(properties)) { var v = key + ' : ' + value + '\n'; text = text + v; } infoTextBlock.setText(text); } catch(error) { console.log(`error occured in SelectItemForGroup = ${error}`); } } SelectItemForGroup();
ListUsers:
When we press on the ListUsers button it will display all the users in the system in a list view. Please note that we are using async function which are implemented in the CodeBehindJs1.js file.
console.log('Calling listUsers'); async function listUserAll() { try { var textBlockControl = TcHmi.Controls.get('TcHmiDatagrid_UserLists'); const result = await listUsers(); var allName = []; for (const [key, value] of Object.entries(result)) { var item = { UserName: key }; allName.push(item); } textBlockControl.setSrcData(allName); } catch(error) { console.log(`error occured in listUserAll = ${error}`); } } listUserAll();
When we click on individual user then we fetch the property of the user such as locale, autologout time etc. Please note that we are using async function which are implemented in the CodeBehindJs1.js file.
async function SelectItem() { try { var gridControl = TcHmi.Controls.get('TcHmiDatagrid_UserLists'); var gridData = gridControl.getSrcData(); var selectedValue = gridControl.getSelectedRowValue(); gridControl.setSelectedRowIndex(gridControl.getSelectedRowIndex()); var properties = await listUserProperty(selectedValue.UserName); var infoTextBlock = TcHmi.Controls.get('TcHmiTextblock_UserDetails'); var text = ''; for (const [key, value] of Object.entries(properties)) { var v = key + ' : ' + value + '\n'; text = text + v; } infoTextBlock.setText(text); } catch(error) { console.log(`error occured in SelectItem = ${error}`); } } SelectItem();
Add User
We can add user by pressing on the AddUser button. Basically it reads the list of users from the system and if the target name is not there then it will add to the system. The framework will store the information to the database, so after the reboot the user name will be there.
Following code are used to inser the user name to the system.
console.log('Calling AddOneUser'); async function AddOneUser() { try { var nameControl = TcHmi.Controls.get('TcHmiTextbox_Name'); var passwdControl = TcHmi.Controls.get('TcHmiPasswordInput_Passwd'); var timeOutControl = TcHmi.Controls.get('TcHmiTextbox_TimeOut'); var groupControl = TcHmi.Controls.get('TcHmiTextbox_Group'); var name = nameControl.getText(); var password = passwdControl.getText(); var timeOut = timeOutControl.getText(); var grp = groupControl.getSelectedIndex(); var emptyString = true; if (!name || name.length === 0) { console.log('name empty'); } const allUsers = await listUsers(); var nameDuplicate = true; for (const [key, value] of Object.entries(allUsers)) { if(key.localeCompare(name) == 0) { nameDuplicate = true; break; } nameDuplicate = false; } if(nameDuplicate == false){ const result = await AddUserEx(name, password, timeOut, grp); console.log(`Add User result = ${result}`); } else { console.log(`Name already found = ${name}`); } } catch(error) { console.log(`error occured in AddOneUser = ${error}`); } } AddOneUser();
Update User
User name cane be replaced with the new name. Following code are used to rename a user.
console.log('Calling UpdateOneUser'); async function UpdateOneUser() { try { var nameControl = TcHmi.Controls.get('TcHmiTextbox_NameUpdate'); var nameUpdatedControl = TcHmi.Controls.get('TcHmiTextbox_NameUpdateName'); var passwdControl = TcHmi.Controls.get('TcHmiPasswordInput_PasswdUpdate'); var timeOutControl = TcHmi.Controls.get('TcHmiTextbox_TimeOutUpdate'); var groupControl = TcHmi.Controls.get('TcHmiTextbox_GroupUpdate'); var name = nameControl.getText(); var nameUpdate = nameUpdatedControl.getText(); var password = passwdControl.getText(); var timeOut = timeOutControl.getText(); var grp = groupControl.getSelectedIndex(); const allUsers = await listUsers(); var nameDuplicate = true; for (const [key, value] of Object.entries(allUsers)) { if(key.localeCompare(name) == 0) { nameDuplicate = true; break; } nameDuplicate = false; } if(nameDuplicate == true){ const result = await UpdateUser(name, nameUpdate,password, timeOut, grp); console.log(`Update User result = ${result}`); } else { console.log(`Name not found = ${name}`); } } catch(error) { console.log(`error occured in UpdateOneUser = ${error}`); } } UpdateOneUser();
Delete User
A user can be deleted in the similar way. When we press on the delete button we pick the name of the user and pass to the RemoveUser method. See the method in code behind file.
console.log('Calling DeleteOneUser'); async function DeleteOneUser() { try { var nameControl = TcHmi.Controls.get('TcHmiTextbox_NameDelete'); var name = nameControl.getText(); const allUsers = await listUsers(); var found = false; for (const [key, value] of Object.entries(allUsers)) { if(key.localeCompare(name) == 0) { found = true; break; } found = false; } if(found == true) { const result = await RemoveUser(name); console.log(`DeleteOneUser User result = ${result}`); } else { console.log(`Name not found = ${name}`); } } catch(error) { console.log(`error occured in DeleteOneUser = ${error}`); } } DeleteOneUser();
HMI access right
Access control can allow security in such a way that different users can access different control. This can be configured in Visual Studio without code. One drawback of this approach is that it must be done in Visual Studio, it can’t be changed later.
=> Create a different User and set the membership of the User. In the following figure, It sets the User as a member of __SystemUsers group with other properties such as Auto logout time, password, etc.
=>If you select a control, properties, and then the lock icon, you can set who can observe the control and who can operate it.
Figure 11: Login user creation and control access set in Visual Studio
YouTube Video
Tips
1: Login page only works with the remote HMI server (actual server), to test all related feature you must publish the content and test it with the HMI server. If you try with live view then it will not work because the framework has not been loaded fully. Also if you use browser (for example chrome) in your local machine, it will not work. You must publish to real HMI server.
2: Many times our changes are not taken into use (for some reason), RESTART HMI SERVER COMPUTER!
3: Insufficient access, many times it is possible that we have created users in the wrong way and when we publish then we get an access error. One solution to this problem is that we can try to delete the contents of the following folder
C:\ProgramData\Beckhoff\TF2000 TwinCAT 3 HMI Server\service\TcHmiProject
It will allow us to reset the password and we can continue publishing it. The case is shown in the following image
4: A working sample of users
=> Guest as __SystemUsers group
=> __SystemUsers group with Symbol Access Read/Write, File Access Read/Write
=> __SystemAdministrators as admin (default)
5: Global Event
=> By global event we can be notified when something changed. We can catch the event and do what is needed to do.
6: Permission to read user info
=> We have some user related function which need permission to read. Some even needs to read config. Here is an example and how to do that.
7: Automatic logout time for Users
=> When we have defined an automatic logout time for each user, if we update the SDK for HMI engineering to another version, then if we change the logout time for user, it can go messy. Our changes may not taken into use. In this case, one option could be deleting the user and creating in different name. Probably those names are somehow integrated into the development environment.
8: More Info related user
=> Lots of information can be adjusted or adapted if we go to Usermanagement section in the server settings. We need to login as Administrator if we want to do some changes as shown in the following figure.
Reference
Download the Sample
Download the sample from the link given above.
Next, let’s try to understand how to manage Alarms and Events at https://www.hemelix.com/scada-hmi/twincat-hmi/alarms-and-events/
Ask questions related to Hemelix sample code and design at Google group https://groups.google.com/g/hemelix