Creating a dashboard for industrial automation

Some quick steps to assemble your first panel information for industrial automation.

Patrick Martins
TotalCross Community
8 min readMay 26, 2020

--

When you start learning to program, one of the first things people will tell you is that developing graphical interfaces (GUI) is complicated. And this is actually very true, especially where performance is a concern. But it is also true that it’s getting easier and easier to build graphical interfaces, given the new frameworks that are emerging. The purpose of this article is to show how to create a dashboard for industrial automation.

The UI is really simple because we wanted to have the maximum performance on ULL and the LOWEST Memory usage.

The framework chosen for this project was TotalCross, following the architectural pattern it guides (to improve maintenance/code adjustments in the future).

Creating the project

The first step is to log in using the VSCode plug-in and create a new project. If you are not registered in this framework, you’ll need to access this link: TotalCross Get Started.

The fields must be filled out by the following:

  • GroupId: com.totalcross.sample.showData;
  • Name: showData;

Within the project, you should access: path>src>main>java>com>totalcross>sample>showData and create the UI and util folders.

Being that:

  • UI folder: where all the user interface (GUI) classes are located;
  • Util Folder: where the classes of constants that will be used during development go.

Then, go to project > src> main and create the resource folders. The files that will be used in the graphical interface (fonts, colors, and images) will be in this folder.

Util

As mentioned, the classes that will be used to create the constants that we will use in the project are in this folder, such as fonts, images, and colors. Creating a class for each of these types of constants is a practice that the framework itself guides (click here to read more). TotalCross guides this pattern to concentrate all images and colors in a single class. In this way, all images and colors are referenced in the same class, which avoids having several classes where the background color is affected several times. This makes it easier to maintain the code since a simple change of interface colors will need to access only one class instead of several.

Back to the code, in this useful folder, you must create 3 files: Colors.java, Fonts.java, and MaterialConstants.java. They add the respective libraries and additional libraries to modify the variables that need to be changed, due to some application specificity.

For example, the Colors.java code:

package com.totalcross.sample.showData.util;
import totalcross.ui.gfx.Color;
public class Colors {
public static int BLUE = 0x4a91e2;
public static int WHITE = Color.WHITE;
public static int GRAY = Color.getRGB(241, 241, 241);
public static int DARK_GRAY = Color.getRGB(50,50,50);
public static final int P_700 = 0x3e72c1;
public static final int P_200 = 0x98c7f1;
public static int BACK = Color.getRGB(254, 254, 254);
}

(G)UI

All user interface classes must be created in this folder.

The ShowMainDataMainWindow.java file must be placed inside this folder. This will be the main file, where we put the interface style and this is where you will set the standards if you want to publish your application for Android or iOS. This class is also called the mainWindow.

These two classes must also be created:

  • ShowDataContainer.java: the file containing the containers that will be displayed on the main interface;
  • TimerContainer.java: Part of the interface where the timer information is shown;
  • ActuatorContainer.java: Part of the interface where the state of the actuators (outputs) is displayed.
  • SensorContainer.java: Part of the interface where the sensor information is shown.

To learn more about what a container is, just click here.

Note: Each part of the application uses the Hbox and Vbox, Edit, Label, Button, and Scroll Container components.

Timer Container

This is the class where we will create the container. The container will be in the middle of your application’s interface; it’s where you define the start and end time of the confinement for one of the GPIO pins, turn on a LED at a certain exit and exclude any of these settings.

This type of application is used in several scenarios, such as control of animal feeders and irrigation programs for plantations.

The code below is part of the TimerContainer code. The same structure is used in other parts of the code.

First, the components are created, and some characteristics are defined.

Then they are added to an Hbox that is added to the centralized screen. Therefore, it is not necessary to add each component individually, which makes the interface cleaner and facilitates the allocation of components.

Edit pintEdit = new Edit(); // edit for recive pin for timer
pintEdit.caption = "GPIO"; // Edit text
pintEdit.setFont(Font.getFont(true, MaterialConstants.TEXT_SIZE));
pintEdit.setMode(Edit.CURRENCY); // set mode numeric
pintEdit.setKeyboard(Edit.KBD_NUMERIC); // lock all non-numeric keys for this edit
pintEdit.setBackForeColors(Colors.GRAY, Color.BLACK);
pintEdit.captionColor = Color.BLACK;
Button savetBt = new Button("Save"); // button for save configuation of timer
savetBt.setFont(Font.getFont(true, MaterialConstants.TEXT_SIZE));
// size
savetBt.setBackForeColors(Colors.BLUE, Colors.WHITE);
Edit starttEdit = new Edit("99" + Settings.timeSeparator + "99" + Settings.timeSeparator + "99");
starttEdit.caption = "Start";
starttEdit.setValidChars("0123456789AMP");
starttEdit.setMode(Edit.NORMAL, true);
starttEdit.setKeyboard(Edit.KBD_TIME);
starttEdit.setFont(Font.getFont(true, MaterialConstants.TEXT_SIZE));
starttEdit.setBackForeColors(Colors.GRAY, Color.BLACK);
starttEdit.captionColor = Color.BLACK;
Edit endtEdit = new Edit("99" + Settings.timeSeparator + "99" + Settings.timeSeparator + "99");endtEdit.caption = "End";
endtEdit.setValidChars("0123456789AMP");
endtEdit.setMode(Edit.NORMAL, true);
endtEdit.setKeyboard(Edit.KBD_TIME);
endtEdit.setFont(Font.getFont(true, MaterialConstants.TEXT_SIZE));
endtEdit.setBackForeColors(Colors.GRAY, Color.BLACK);
endtEdit.captionColor = Color.BLACK;
HBox box = new HBox(HBox.LAYOUT_FILL, HBox.ALIGNMENT_STRETCH);
box.add(starttEdit);
box.add(endtEdit);
box.setSpacing(MaterialConstants.COMPONENT_SPACING);
// add components for timer
sc.add(timerLb, CENTER, TOP, sc.getWidth(), MaterialConstants.EDIT_HEIGHT);sc.add(box, LEFT + MaterialConstants.BORDER_SPACING, AFTER + aterialConstants.COMPONENT_SPACING,FILL -MaterialConstants.BORDER_SPACING, MaterialConstants.EDIT_HEIGHT);box = new HBox(HBox.LAYOUT_FILL, HBox.ALIGNMENT_STRETCH);
box.add(pintEdit);
box.add(savetBt);
box.setSpacing(MaterialConstants.COMPONENT_SPACING);
sc.add(box, LEFT + MaterialConstants.BORDER_SPACING, AFTER + MaterialConstants.COMPONENT_SPACING,FILL -MaterialConstants.BORDER_SPACING, MaterialConstants.EDIT_HEIGHT);

Access the complete code here.

Actuator Container

This container shows the current state and value of the actuators, and allows them to trigger and change the GPIO pins, which were defined as the output.

This is typically used for digital units, such as turning on a lamp or opening a gate, and by manually changing the value, it is possible, for example, to define the lighting intensity of a lamp or the speed of rotation of an engine.

The biggest change in this container is that using Vbox, it is possible to place all components with the same size in a single Vbox. Adding each set to the screen is easier, and the code is even smaller than the form used in the TimerContainer.

The following code shows the difference better:

VBox boxi = new VBox(VBox.LAYOUT_FILL, VBox.ALIGNMENT_STRETCH);
boxi.add(indicator1);
boxi.add(indicator2);
boxi.add(indicator3);
boxi.add(indicator4);
boxi.setSpacing(MaterialConstants.COMPONENT_SPACING);
sc.add(boxi,RIGHT-MaterialConstants.BORDER_SPACING, AFTER+MaterialConstants.COMPONENT_SPACING,MaterialConstants.EDIT_HEIGHT,(MaterialConstants.COMPONENT_SPACING + MaterialConstants.EDIT_HEIGHT)*4);VBox boxv = new VBox(VBox.LAYOUT_FILL, VBox.ALIGNMENT_STRETCH);
boxv.add(value1);
boxv.add(value2);
boxv.add(value3);
boxv.add(value4);
boxv.setSpacing(MaterialConstants.COMPONENT_SPACING);
sc.add(boxv,BEFORE - MaterialConstants.BORDER_SPACING, SAME, PREFERRED, boxi.getHeight());
VBox boxa = new VBox(VBox.LAYOUT_FILL, VBox.ALIGNMENT_STRETCH);
boxa.add(Actuator1);
boxa.add(Actuator2);
boxa.add(Actuator3);
boxa.add(Actuator4);
boxa.setSpacing(MaterialConstants.COMPONENT_SPACING);
sc.add(boxa,LEFT + MaterialConstants.BORDER_SPACING, SAME, boxv.getX() -MaterialConstants.COMPONENT_SPACING*3, boxv.getHeight());

Access the complete code here.

Sensor Container

The SensorContainer allows you to see the current state/value of the sensors if you are receiving data, but you can also link it to an actuator (output). The user can define an activation and deactivation value for the pin, and then activate or deactivate the actuator when there is an input that meets the requirements indicated by the user.

This is a common working process used for thermostats.

This screen has the same characteristics as the previous one, with similar vertical components in the Vbox and horizontal components in the Hbox.

Click here to access the complete code.

Show data container

And finally, the class gathers all the previous containers and displays them to the user.

To call the containers, just treat them as the object of the Lib ScrollContainer, as they were created as an extension of it. See below:

package com.totalcross.sample.showData.ui;
import com.totalcross.sample.showData.util.Colors;
import totalcross.sys.Settings;
import totalcross.ui.Container;
import totalcross.ui.Label;
import totalcross.ui.ScrollContainer;
import totalcross.ui.font.Font;
public class ShowDataContainer extends Container {
private ScrollContainer sc;
@Override
public void initUI() {
// add containers
sc = new ScrollContainer(true, true);
sc.setBackColor(Colors.GRAY);
add(sc, LEFT, TOP, FILL, FILL);
TimerContainer tcont = new TimerContainer();//
instantiating TimerContainer
ActuatorContainer Acont = new ActuatorContainer();//
instantiating ActuatorContainer
SensorContainer Scont = new SensorContainer();// instantiating SensorContainer
sc.add(Acont, CENTER, TOP + (int) (Settings.screenHeight * 0.15), (int) (Settings.screenWidth * 0.3),(int)(Settings.screenHeight * 0.8));// add Actuator containersc.add(tcont, CENTER - (int) (Settings.screenWidth * 0.02) - Acont.getWidth(), SAME, Acont.getWidth(), Acont.getHeight());//add Timer containersc.add(Scont, CENTER + (int) (Settings.screenWidth * 0.02) + Acont.getWidth(), SAME, Acont.getWidth(), Acont.getHeight());//add Sensor container// APP Title
Label Title = new Label("Show Data");
Title.setFont(Font.getFont(true, (int) (0.05 * Settings.screenHeight)));// add Title label
sc.add(Title, CENTER, TOP + 10);
}
}

ShowDataMainWindow

This file is where the basic characteristics of the application are defined, as seen in the following code.

The components follow the FLAT_UI style because it reduces the RAM consumption of the application due to its simple design. If there are no limitations on memory consumption, other sets of components with more elaborate styles can be used, such as MATERIAL_UI and ANDROID_UI.

package com.totalcross.sample.showData;import totalcross.ui.MainWindow;
import totalcross.ui.font.Font;
import com.totalcross.sample.showData.ui.ShowDataContainer;
import com.totalcross.sample.showData.util.Fonts;
import totalcross.sys.Settings;
import totalcross.sys.Vm;
public class ShowData extends MainWindow {
public static final String version = "1.0.0";
public ShowData() {
super("Show Data", NO_BORDER);
setUIStyle(Settings.FLAT_UI);
setDefaultFont(Font.getFont(Fonts.FONT_DEFAULT_SIZE));
}
static {
Settings.applicationId = "SDSA";
Settings.appVersion = version;
Settings.iosCFBundleIdentifier = "com.totalcross.mocknbuild";
}
@Override
public void initUI() {
super.initUI();
ShowDataContainer container = new ShowDataContainer();
swap(container);
}
}

This was the last class needed to assemble the panel interface for industrial automation.

If you want to download the application to test, just download it on TotalCross’ GitHub.

Results

The final result of the project can be found in the images below:

PS: Due to its simplicity, the Vscode plug-in was used to run the application. With the TotalCross simulator, it is possible to adjust the resolution and the density of pixels; thus, you can check previous results before running it on the device.

In the images below, you can see the result on the Toradex Aster + Colibri imx6ULL module:

It’s also possible to run it on other platforms, such as Raspberry pi, the exemple of next image, or beagle boards.

Raspberry pi 3 b+

This example demonstrates what would be a “front end” (GUI) for an embedded application that accesses the GPIO.

It is possible to take advantage of the logic applied in this example in very different ways, depending on the need for information, the design of the application, and how much memory can be consumed.

It is worthwhile testing with UI material since the memory increase is not so significant (this is what I plan to publish about in the future).

So, I hope this article helps you in your mission to create a dashboard for industrial automation. If you have any questions or tips, please feel free to comment here.

--

--