Technical Article

Advanced Web-Based HMI Project: Part 1

September 27, 2023 by Michael Levanduski

Taking web-based HMI design from a simulated project to the real-world environment. Part 1 introduces tag instances and attributes.

In an earlier series, a simple end-to-end web-based HMI solution was proposed. However, scaling that solution to accommodate real-world use cases would be a recipe for failure, frustration, and burnout, since it was designed for a simulated environment. In this newer series, I’d like to propose some modifications to give the solution a better shot of surviving ‘in the wild.’

Some tools that we will arm our solution with to combat the chaotic world are object-oriented programming principles, a relational database, and basic security. Let’s begin by dissecting the starting point of any HMI project; the edge layer.

 

Edge Computing Layer Scalability

The single device and tag scenario used in the previously described project simply isn’t realistic in the production setting, although one may dream in envy of such a simple setup.

 

Simple single tag scenario

Figure 1. Edge computing layer of the earlier solution proposed in the earlier Web-based HMI series. Image used courtesy of the author

 

In an industrial setting, multiple devices are often connected within multiple subnetworks that form the company’s industrial control system (ICS) network. These devices could have hundreds of tags and employ all sorts of communication protocols. Yikes, where to begin unraveling the spaghetti?

Integration is no small feat, and as such, a template or modular approach in modeling devices and tags is essential. This is where object-oriented programming principles come into play.

 

Multiple tag and device scenario

Figure 2. Edge computing layer of a more complex, realistic system. Image used courtesy of the author

 

Device and Tag Environment Setup

As with the beginning of all great productions, the stage needs to be set. In an integrated design environment, we will create a folder structure as follows:

<projectname>/models

 

The projectName placeholder can be anything you prefer. In this directory, we will add 2 files below:

  • device.py
  • tag.py

In visual form, this should appear as such:

Project file structure

Figure 3. The model file structure. Image used courtesy of the author

 

Creating a compartmentalized development environment would be a great next step in case we set fire to our stage and don’t want it to spread to the rest of the building. This will make explaining the work completed much easier as well. To accomplish this, a virtual environment can be created, as discussed in another previous article.

 

Tag Class

With the stage set, we will learn how to crawl into the intro and start at the smallest building block of our architecture; the tag. The more traditional OPC-UA tag is modeled as an object and has a plethora of attributes and properties. We’ll keep it much simpler, but the principle can be easily extended. The tags in our use case will only have a name and a value, taken not from a real-world tag just yet, but from a randomly created integer as a placeholder for now. We’ll need to make a model or general template of what a tag looks like. To do so, we will navigate to the tag.py file in our aptly named models directory:

Project tag.py file

Figure 4. The tag.py file in the model structure. Image used courtesy of the author

 

Some Python code below should get us started:

from random import randint

class Tag():
    def __init__(self,name: str):
        self.name = name

    def value(self):
        return self.generate_value()


    def generate_value(self):
        return randint(1,100)

 

Some of the syntax may seem obscure for those of you unfamiliar with object-oriented programming. Let’s focus on the big-picture ideas of what’s actually happening here.

  • Declared a Tag template by using the class keyword
  • The Tag name is tied to an instance attribute self.name
  • The Tag value calls a method 'value' when asked what the real-time value of the tag is

If this is a bunch of gibberish that’s ok. It was for me, too when I first started. Seeing the implementation is a better teacher. A big-picture concept to grasp is that the tag.py file does not perform any action when asked to run; it’s not lifting a finger to help us:

Running the tag.py file

Figure 5. Results of running the tag.py file. Image used courtesy of the author

 

Well then, why the heck did we create this class in a tag.py file? We created it to define instances of the Tag class.

 

Tag Instance

To see this in action, create a second file called tagInstance.py in the root project directory:

<projectname>/tagInstance.py

 

This should resemble something similar to the below:

Tag instance structure

Figure 6. Creating a single tag instance. Image used courtesy of the author

 

Within the tagInstance.py file, let’s leverage the template we created in the tag.py models file:

from models.tag import Tag

 

With the relative import referencing the Tag class, let’s create an instance from the mold:

flow = Tag('FlowmeterA')

 

We will assign a variable flow to represent the Tag object name FlowmeterA. Let’s try and access the actual flowrate value of FlowmeterA:

print(flow)

 

When all three lines are executed together, you’ll notice we get a somewhat odd-looking address that we didn’t expect:

Returning tag memory location

Figure 7. Tag memory location in hex format. Image used courtesy of the author

 

Our variable flow is not a simple variable pointing to a single value. It’s more sophisticated and rich than just a number. It’s a Tag object hanging out at the hexadecimal memory location on your local machine, with access to attributes and methods at its disposal. Let’s first access the name attribute.

from models.tag import Tag

flow = Tag('FlowmeterA')
print(flow.name)

 

Notice how it returns the name attribute or property we passed to the Tag() class:

Returning tag name attribute

Figure 8. Tag name attribute in plain text. Image used courtesy of the author

 

Now, what about the tag value? Let’s try calling the value method of the instance:

from models.tag import Tag

flow = Tag('FlowmeterA')
print(flow.name)
print(flow.value)

Returning tag value attribute

Figure 9. Tag value attribute, but only the memory location. Image used courtesy of the author

 

Again, we got something that isn’t very easy on the eyes. Our FlowmeterA tag instance is telling us that it has access to a method value that can give us the quantitative measure but is holding out on us. So how do we access the quantitative value as if it were a property similar to the name attribute? We can add a property decorator (denoted with the @ symbol in the code) within the models/tag.py file, thus creating a “getter” method:

models/tag.py file

 

from random import randint

class Tag():
    def __init__(self,name: str):
        self.name = name

    @property
    def value(self):
        return self.generate_value()


    def generate_value(self):
        return randint(1,100)

 

Now, when the value method associated with the FlowmeterA tag is called from the tagInstance.py file, it will share the quantitative value:

Returning numeric value attiribute

Figure 11. Tag value attribute, now provided in numeric format. Image used courtesy of the author

 

The property decorator makes class methods behave as if they were an attribute of the class instance. Pretty neat trick!

 

Next Steps: Device Classes

Hopefully, this introduction gave some insight into the modularity and power of classes in object-oriented programming. The Tag class will serve as a compositional building block for the device class methods. The device class will be completed in the next section, and the class definitions will then be combined to model a choreographed edge layer.

 

Featured image used courtesy of Adobe Stock