In the previous post I talked about why we should use object oriented for PLC programming instead of the solely structured programming model. In this post I will explain how to do it to achieve the best result.
The architecture of a software is the shape that is given to that software. That shape is constructed by dividing that software into different components and the ways in which those components communicate with each other. The purpose of this shape is to facilitate the development, commissioning and maintenance of a software product. That statement may wonders you. Perhaps you thought that the goal of software architecture is to make the system work properly. Without any doubts, the software architecture must support that as one of its highest priorities but this is not the final goal.
There are many PLC programs out there, with terrible architecture, that work just fine. Their main problem is not their operation, their problem is their development, commissioning, maintenance and ongoing development. Even though the architecture plays rule in supporting the proper behavior of a software, the primary purpose of the architecture is to support the life cycle of the system. Good architecture makes the system easy to develop, understand, test, launch, maintain and extend. The final goal is to minimize the lifecycle cost of the system and to maximize programmer productivity.
In this post I won’t explain OOP basics such as class and object. There are some content about them out there. Instead, I will show you how to architect your automation program to achieve the main goals that I mentioned above using OOP. Therefore I suppose you know the object oriented programming principals.
As I said, a good architecture most support:
- The operations of the system.
- The commissioning of the system.
- The maintenance of the system.
- The ongoing development of the system.
The first bullet , operations, means the architecture of the system must support the operations of the system. If our system is about mold injecting, the architecture must support mold injecting operation. Furthermore, a good architecture clarifies and exposes the intent of the system, so that it is visible clearly. When a developer open the mold injection project, he or she will not have to hunt for behaviors because those behaviors are first-class elements at the top level of the project. They might be FCs, FBs and data structures that have top positions in the project structure with names that clearly describe their functions and responsibilities. That means, a good architecture screams what a system does.
Leaving Options Open
A good architecture leaves options open and allows the system to change easily. In realty it is hard to leave all options open. When you are programming for a concrete batching machine which must be deployed into many different factories all around the world, with features that might happen in the future, you do not know what those features can be.
Nonetheless, there are principles of architecture that are relatively inexpensive to implement and help us to make programs and machines easy to change by leaving the options open.
Single Responsibility Principle
“Single Responsibility” may sounds very easy to understand but I believe it is not so. It can be defined differently in two different areas:
- Programming and
In programming, when we say a function is single responsible, it means that function does one and only one specific operation but in architecture the single responsibility means to separate those things that may change for different reasons, and to collect those things that may change for the same reasons.
What change for different reasons? There are some obvious things. IO accessing methods change from local to distributed IOs. From PROFIBUS to PROFINET. User interfaces can also change for reasons that have nothing to do with machine main operations. Clearly a good architecture will separate the IO and UI form the machine operations in a way that they can be changed independently without affecting each other.
A good system architecture is one in which IO accessing and HMI communicating decisions are not important at all. If your mold injection machine program has good architecture, it is not important for you that the carriage position comes from an analog ruler or a shaft encounter. Once again, It is not important for you that the carriage moves by a hydro-motor or a servo motor.
Thus we find the system divided into decoupled layers – machine operations, IO accessing methods, UI, storing and retrieving data methods, etc.
What else change for different reasons? A concrete bricks production machine, produces concrete and then produces bricks using that concrete. These two operations can change for different reasons. The way we produce concrete – for example the order we dose and mix materials – can change to make a higher quality concrete faster while the bricks process can change for another reasons. Consequently we should divide the operation of this machine into two different sections, concrete production and bricks production.
Decoupling Operations from Machine Objects
Do things that can change for different reasons finished. Of course not. The concrete production part of the bricks machine uses 4 bins for dosing different materials, a conveyor and a mixer. While the concrete production process has no changes, the conveyor operation may change from direct motor starting to VFD to reduce power consumption and mechanical vibrations. Hence It’s wise to separate the overall logic of concrete production operation from machine objects. I mean the hoppers, conveyor and mixer logic. In this architecture, adding new operations or changing the logic of current operations and machine objects would have the least amount of side effects and we managed to leave options open as much as possible. Be patient! I will show you how we do it in a minute.
Obviously it was possible to work on the same PLC program by multiple programmers at the same time using the standard IEC languages. We could define FBs and FCs input/outputs and assign the implementations to other programmers but using interface of object oriented, it is by far easier. There is no need to define empty FC/FBs to continue development while the original FC/FBs are not ready yet.
Robert. C. Martin introduced the Clean Architecture concept in the “Clean Architecture” book in 2017. I borrowed most of the ideas of this post and even some paragraphs from the Clean Architecture. After reading this book several times and applying its rules in action, in iOS mobile application development industry, I tried to convert the clean architecture concepts to industrial automation counterparts.
The Dependency Rule
Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes FCs, FBs, data structures, variables, or any other named software entity. We do not want anything in an outer circle to impact the inner circles.
Entities generally are data structures that all your machine operations work based on them. To search and find them in your machine, suppose there is not any automation systems and all the operations must be done using labors manually. There is still some data structures that manual operations must be done regarding them. For example in a concrete batching machine, the recipe defines the specifications of the output concrete. Thus the recipe is an entity for the concrete batching machine.
Entities layer contains all the system entities and is placed in the core of the clean architecture. That means entities must not depends on outer layers. Outer layers must depend on them. Entities can depend only on each other. Note that your system entity might be simply an Integer number or even the system may have no entities.
A system operation is a description that specifies the input to be provided by the user, the output to be returned to the user and the processing steps involved in producing that output.
In the main definitions of clean architecture, this layer is called Use Cases. But I believe System Operations is a better name at least for industrial machines so I called it system operations.
We do not expect changes in this layer to affect the entities. We also do not expect this layer to be affected by changes to external layers such as CPU, IO or network changes. The system operations layer is isolated from such concerns.
The software in the interface adapters layer is a set of adapters that convert data from the format most convenient for the system operations to the format most convenient for the external services such as UI and hardware.
All the code that converts real IO addresses to meaningful names goes in this layer. Also the codes that use platform and hardware specific services must be restricted to this layer. Things like PID controllers, network communications and high speed counters. By this way you isolate all your automation operations from hardware specifications. It provides lots of benefits.
The outermost layer is hardware. Generally you do not need to write much program here. All setups and configuration codes to utilize hardware services goes in this layer.
Only Four Circles
The circles in clean architecture figure are intended to be schematic. You may find that you need more than just these four. There’s no rule that says you must always have just these four. However, the Dependency Rule always applies. Source code dependencies always point inward. As you move inward, the level of abstraction increases. The outermost circle consists of low-level details. As you move inward, the software grows more abstract and encapsulates higher-level operations. The innermost circle is the most general and highest level.
I talked about dependency rule and emphasized on the indecency of the inner layers from the outer layers. But how would the inner layers communicate to the outer layers without knowing anything about them. The answer is polymorphic interfaces.
The above image is a UML diagram. It’s a good idea to learn UML to illustrate your software visually but if you do not familiar with it I explain this diagram for you. A machine operation needs some inputs and outputs. We define the inputs and outputs as two different interfaces. These interfaces belong to that operation and lay in the system operations layer. One or two different IO access FBs implement those interfaces to provide proper inputs and outputs for the operation.
Which Data Crosses the Layers
Typically the data that crosses the layers consists of primitive data types like bool and integer or simple data structures. These data can also simply be arguments in function calls.
Amongst discrete, continues and batching processes, batching is the most difficult to implement. Even a very simple batching PLC program can turn into a very complex project easily. You will find batching processes in food and beverage, pharmaceutical, concrete and other industries. So for this example I imagine a simplified concrete bricks production machine. This machine produces concrete using raw materials then produces bricks by pressing the concrete and transferring the pallets of bricks to the storage automatically.
Concrete Bricks Entities
Let’s analyze the program architect. First we should find the entities, and operations. The main entities of concrete bricks production is the recipe for concrete production and the number of bricks pallets to produce. I put both of them in a simple data structure and call it Bricks Production Request. There is also a batch report that shows the actual loaded values to report how much precise was the produced concrete. The batch report is calculated after each batch and is stored in a data base to produce reports and invoices. These operations are normally managed by the monitoring system so PLC program sends the batch report to the monitoring system. We collect all production data in another data structure and call it Bricks Production Report.
Concrete Bricks Operaions
The responsibility of this machine is bricks production. So the main operation is BricksProduction. Notice how I use naming to scream what this machine does. As I explained before, to produce concrete this machine first produces concrete and then press the concrete to produce bricks. So the BricksProduction will use ConcreteOperation and WetPressOperation to produce the bricks. Obviously these operations use the services of some lower level operations to produce concrete and press the bricks. I called these lower level operations AggregatesLoader, AggregatesTeransfer, ConcreteMixer, WetPress and BricksTransfer.
As you see the in the above figure, the system operations layer divided into three layers. If I wanted to implement this project in practice I added a forth red layer to show an operation for AggregateHopperLoader. I just omitted that for the sake of simplicity. As I said before we can divide each layer into sub-layers. Just do not forget to obey dependency rule.
AggregatesLoader Operation Details
Now let’s focus on AggregatesLoader operation. The AggregatesLoader is an operation that provides aggregates loading service for a higher operation – concrete production. IAggregatesLoader provides this service by implementing the IAggregatesLoader interface. For providing that service it needs three other services – IAggregateLoaderOutputProvider, IAggregateLoaderInputProvider, IAggregateLoaderPresenter. These services are provided by three service providers with the same name without “I” prefix. There is similar scenario fo ConcreteProdution operation. It also needs some other services to do its operations.
I would like to notice again that the classes and interfaces in system operations layers must not know anything about CPU, IO, networking and anything else that is related to technical details. An operation focuses only on the operation and nothing else. For example the AggregatesLoader program controls things like this:
- The start, stop and pausing of aggregates loading. (As I said, there must be AggregateHopperLoaders to provider loading from hoppers operation service for AggregatesLoader.)
- The timing between loading from different hoppers.
- The fault management for example where there is an empty hopper.
- Producing values for presenter to show online values like the amount of loaded aggregates.
- Telling the client operation that the aggregate loading operation did end, etc.
Consequently, the AggreatesLoader does not know how and where IOs come and go. This is the responsibility of IO providers. It also does not know anything the details of representing data. Aggregates Loader only provides required representation data for IAggregatesLoaderPresenter. It is the responsibility of presenters to deliver that information to the monitoring system communication service.
The question is “who will instantiate and connect services and operations together and where is it in the clean architecture circles?”. Let’s call it Composer. It is a programming module that is responsible to instantiate operations and services and connects them together to make your program ready to work.
Composer does not lay in the clean architecture layers and dependency rule does not apply to it. The responsibility of the the composer will end after your PLC program start up phase finishes and it will not do anything during the machine operation. Thus avoid implementing machine operations program or IO access program inside composer. It is dependent on all types a in all layers. You should have at least one composer in your PLC program. Of curse you can have several composers to compose different parts of the machine but ultimately you need to have one composer that uses the other composers to build up whole software project at runtime.
Too many types and files
As the first pitfall you may think using clean architecture, you will have lots of types and files in your project, like FBs, interfaces and data structures. You may think these a large number of files makes your project unreasonably complex. Yes, you will have have lots of files in your project but if you look at the names of these files you will find their names completely self-descriptive, as a result you or any one else who want to maintain or develop your project will find the details very easily. Furthermore, if you look inside these files you will see there are not a large number of lines of program.
Thanks to single responsibility principal, those are single responsible operations or services. So those can be read, fixed and developed very easily. Actually, having a large number of types separated in different files with small amount of program in each is a sign of a good architecture.
Let’s omit composers
The next pitfall is the temptation of omitting composers and instantiating services in the declaration part of operations. You may think this behavior makes your program easier to write and understand. Practically by doing so you have ignored the dependency rule. Because your higher degree objects know about your lower degree objects.
What will happen if you do so? If you define and pass them to the operations directly, without using interfaces, you will loose testability and modularity completely. But if you define services as interfaces and pass service instances to the interfaces you won’t loose testability and modularity. But by instantiating FBs in the variable declaration part of the operations you can’t make decision about your composition and you will lose computed composition ability that composers provide for you.
Clean architecture adds some important traits to your program. One of them is modularity. This form of thinking helps us to divide codes into Independent modules. Combination of independent modules that use polymorphic interfaces to communicate with each other makes a lot of benefits with I call them MR. TER in abbreviation.
Modularity means dividing programs into smaller parts that can be modified or replaced without affecting the other parts of the program.
Readability is a product of modularity but clean architecture enhances it a lot more. A good architecture gives you as a programmer all information about the system. It describes what is the main propose of this system and how this goal is satisfied in more details when you move through operations and services. For me, that forget what I have done two weeks a go, it is a key feature. A good programmer is a programmer who writes programs to be read and maintained easily by the other ones.
Have you ever engaged in commissioning of industrial automation projects on the sites in the middle of no where while the clients are in hurry and each test costs a lot of materials. In these hard situations you have to find bugs and fix them. I believe it is one of the most stressful conditions that some one can deal with. So it is obvious how much testability is important. Using polymorphic interfaces you can test almost all important parts of your program using a simple simulator without having access to any IOs, networking, hardware and utilities like hydraulic pressure and so on.
Suppose you want to test the AggregatesLoader. This class communicates with the outside world using three interface that act as external services for it.
For example using an enumeration I can config my PLC to simulate the operation with all hardwares but without raw materials. In this case the composer will pass mock IO providers that simulate the aggregate loading IOs.
You can develop the MockAggregateIOProider class that implements the IAggregatesLoaderInputProvider, IAggregatesLoaderOutpuProvider and IAggregatesLoaderPresenter and pass the mock instead of the real service providers. When AggreagateLoader requests to load aggregate from hopper 1, the mock instance sends the simulated feedback and increases the amount of weight gradually as if real material is being loaded.
In test mode you can pass your mock objects, rather than real objects, to test different parts of your program. Notice that in this form of testing you do not need to touch the main programs of the project. You just need to write some mock classes and pass them in composition instead of the real instances which is very handy.
In this architecture, regarding the kind of change, only some specific classes will change. It helps us to only concentrate on modifying and testing that classes. Unchanged part of the app does need to be checked and tested. So it makes programming life very easy.
Using clean architecture, many parts of your program do not know anything about the hardware they are running on and also do not know about other parts of your program. So you can place them in one or more libraries and use those libraries in similar projects. How much it can be enjoyable if you work in a company that produces many varieties of the same machines.
Software architecture is a professional skill. If you want to master it, you should first study then practice and repeat this cycle several times.I know most of you are not software engineer, may be you are electrical engineers. So dealing with software architecture design and object oriented principles might not be very easy for you to digest at first. But let me tell you two truths. First of all, even many software developers who have graduated with software engineering degrees and work in IT industry for many years does not know and use these principals well. I have worked with many of them and their software architecture skill is horrible. And second, I am also an electrical engineer. Hence you are not alone.
I hope this post helps you to design and develop better automation systems. If you have ideas or questions about the above subject leave comment below.