Creating a dashboard for industrial automation

Patrick Martins
May 26 · 8 min read
Image for post
Image for post

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 panel interface 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;

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;

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;

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.

Image for post
Image for post

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

Image for post
Image for post

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

Image for post
Image for post

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:

Image for post
Image for post

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:

Image for post
Image for post

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

Image for post
Image for post
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).

TotalCross Community

Focused on publishing articles and tutorials about…

Patrick Martins

Written by

TotalCross Community

Focused on publishing articles and tutorials about cross-platform software development ( Embedded systems, Android, iOS, and others) using TotalCross Java framework.

Patrick Martins

Written by

TotalCross Community

Focused on publishing articles and tutorials about cross-platform software development ( Embedded systems, Android, iOS, and others) using TotalCross Java framework.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store