Advanced Web-Based HMI Project: Part 1
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.
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.
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:
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:
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:
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:
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:
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:
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)
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:
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:
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