This content originally appeared on Level Up Coding - Medium and was authored by Giuseppe Pio Cannata
Industrial automation is a key area in modern industry, as it can optimize production process, increase efficency and reduce the operating costs. By integrating advance technology such as PLC (Programmable Logic Controller), SCADA (Supervisory Control And Data Acquisition), IIoT (Industrial Internet of Things) and robotics, companies can achieve greater flexibility and productivity in their plants.
At the base of it all, software plays a key role. The increase complexity of industrial plant requires an structured approach to programming, which is why adopting a framework is crucial.
A well-designed framework must be able to offer the following benefits:
- Modularity and Reusability: Well-structured software is divided into indipendent modules, facilitating code reuse and maintenance.
- Scalability: The ability to extend the system without rewrite entire portions of code is essential to adapt to system evolutions.
- Maintainability: Well-organize code reduces the errors and simplify the debugging, improving system reliability
- Standardization: The use of common guidelines and templates encourages the collaboration among different development teams
1. Pack ML
PackML (Packaging Machine Language) is a standard develop by OMAC (Organization for Machine Automation and Control) for machines programming in industial enviroments.
It is designed to simplify programming, management and communication between different machines and systems, improving their control and maintenance over time.
The goals can be summarized in the following points:
- Standardization: Create a common behavior by making it easy to integrate machines from different vendors
- Interoperability: Improving communication between machine and control systems, enabling different machines to “talk” to each other and to centralized monitoring and management systems.
- Performance Optimization: Optimizing operations, reducing downtime, improving resource management.
- Industry 4.0 Facilitation: Support digitization, data collection and real-time communication between machines and centralized systems.
2. Standardization of machine behavior
Let us now delve into how PackML standardizes the behavior of machines. The standard defines a set of specific states in which, a machine and its subcomponents (Units), can be during the operative cycle.
2.1. Machine states and transitions between them

Fig.1 shows the different possible states of machine (called OMAC states) and the possible transitions between their. In particular, the colors represent the behaviour associated whit each state:
- Green states: these are transitions states, the phase in which the machine is actived and do the operations to continue in the operative cycle
- Blue state: is the operational state (EXECUTE) in which the machine is performing its normal operative cycle
- Yellow state: represent the finished states, in which the machine has completed its operations and is waiting for a specific command to move to the next state. Among these we have:
- Initial state (IDLE): the machine is ready to be started, but it’s not running
- Completed state (COMPLETE): The machine has completed production
- Stop State (STOPPED): The machine is stopped, but can be restarted
- Abort state (ABORTED): The machine is stopped due to a problem
The PackML framework provides for active operator interaction with the system, in addition to the normal automatic machine cycle. Looking at Fig.1, we can see that there are arrows between the various states that represent possible transitions.
Transitions labeled SC (State Completed) indicate that the current state, once the intended operations are completed, evolves autonomously to the next state, without any external intervention.
In contrast, transitions highlighted in red represent actions that can be triggered by the operator, but do not necessarily have to be.
On what does this difference depend?
Consider, for example, the Stop and Abort transitions.
Traditionally, the Stop transition is linked to the operator pressing the stop button. However, the programmer can configure the system in a more advanced way by providing that the Stop is also triggered automatically in response to certain abnormal process conditions.
For example, suppose that a product X has to pass through sensor A first and within 30 seconds, sensor B as well. If this does not happen in the allotted time, the system can automatically trigger the transition to the Stop state, without the manual intervention.
The same principle applies to the Abort transition, typically associated with critical error situations and the emergency button.
To better understand, let us analyze a practical example:
Let us imagine that the machine is in the Execute state, i.e., in full operation. At some point, an actuator detects an abnormality, such as extra torque, and generates an error. In this case, to prevent possible damage, the programmer must have configured the system so that this type of error automatically triggers the transition to the stop state.
The machine will then enter the Aborting state, during which it performs all necessary operations to safely stop the motors and bring the entire system to a safe condition. Upon completion of this phase, the system transits into the Aborted state, which represents a stable but non-operational condition from which it cannot automatically restart.
At this point, operator intervention is required, and the operator must press the Reset button. The machine will perform the Resetting operations, returning to the Idle state, ready for a fresh start.
Finally, to start the production cycle again, the operator must press the Start button. The system will then enter the Starting state, during which all preliminary operations will be performed, until it returns to the Execute state, resuming normal operation.
2.2. Distinction between Hold and Suspended
The Hold and Suspended states represent two pause conditions that a machine can assume during the normal operating cycle. In both cases, the machine temporarily suspends activities while waiting for certain conditions to occur in order to resume the process.
However, it’s important to distinguish clearly between the two:
- The Hold state is used when the machine cannot continue activity due to an external, but not critical, temporary condition, thus not such as to require an Aborted or Stopped state. It is an automatic transition, triggered by the system itself. A practical example might be a lack of material to process, or a momentary blockage of a downstream machine that causes the upstream one to suspend execution while waiting for the flow to resume
- In contrast, the Suspend state represents a manual pause in the cycle, intentionally requested by an operator or by a command from the supervisory system. A typical case is when a visual product inspection or scheduled check is performed
In summary, while Hold is an automatic pause due to external operating conditions, Suspend is a voluntary and controlled pause by the operator or system. Both allow more flexible and safe management of the production process without completely interrupting the machine cycle.
2.2. Distinction between Stop and Abort
One question that might arise is: What is the distinction between Stop and Abort?
When we talk about Stop, we mean an Stop in phase, or rather, a controlled stop of the system. In this mode, the machine neatly terminates ongoing operations, bringing each component to a safe condition. An example might be as follows: Suppose we have a labeling machine that applies labels to water bottles. If the operator presses the stop button while the machine is starting the labeling of a new bottle, the system does not stop immediately. Instead, the machine completes the labeling in progress, after which it performs the necessary movements to bring itself to a safe condition (e.g., bringing the mechanisms to their initial or rest position). Only then, the stop of the machine cycle is made. This type of stop, is typically used in contexts where there is no imminent danger, but it is still necessary to interrupt the production cycle. This transition allows the machine to safeguard ongoing production and maintain internal state consistency.
Abort, on the other hand, is the most drastic transition. It represents an immediate and uncontrolled interruption typical of emergency situations. It is triggered when a major failure or a condition hazardous to people, machines or products occurs.
In Abort mode, the first priority is safety: all operations are stopped instantaneously, without regard to the current state of the machine.
Subsequently, manual intervention and explicit resetting by the operator is often required to resume the production cycle.
2.4. Units
To ensure modularity, scalability, and better diagnostics, PackML introduces the concept of Units, dividing the machine into independent subsystems. Each Unit operates autonomously with its own states, but remains synchronized with the overall system.
In fact, at the hierarchical level, we will have a Unit Zero (which we will call Machine for simplicity), responsible for managing the overall OMAC state of the machine. The other Units will model specific parts of our machine, maintaining a dependency on the Machine to ensure a coordinated and standardized workflow.
3. From theory to programming: Modeling the PackML
Let’s focus on figuring out how to transport what we have learned so far into programming. To do this, suppose we need to model a machine divided into two units.
To make the discussion more generic and accessible, I will use Python instead of a tool specific to the automation world. I have chosen not to adopt typical object-oriented programming concepts, such as inheritance and polymorphism, to keep the code simpler and more straightforward.
Through the implementation of a class, I simulated the operation of a PLC, replicating its typical behavior. The concepts developed are easily adaptable and reusable in different contexts.
The developed code is available at the following link.
3.1 Implementation of Units
Below are the data structures that we will use throughout the article.
class ST_REQUESTS:
State : int = 0
Mode : int = 0
class ST_UNITS_FDKs:
Started : bool = False
Aborted : bool = False
Stopped : bool = False
Resetted : bool = False
Completed : bool = False
Suspended : bool = False
Unspended : bool = False
Holded : bool = False
Unholded : bool = False
class ST_MACHINE_CMD:
Start : bool = False
Stop : bool = False
Abort : bool = False
Reset : bool = False
Suspend : bool = False
class ST_State:
Text : str = ''
Value : int = 0
class IO_Signals:
start : bool = False
stop : bool = False
emergency : bool = False
reset : bool = False
Machine

As anticipated, the Machine represents Unit 0 and is responsible for coordinating the other Units. To do this, it needs two basic variables:
- Units_Fbks: a variable of type ST_UNITS_FDKs, used to collect feedback sent by the Units. This feedback allows the Machine to verify the completion of operations in the individual transition states.
- Actual_State: variable of type ST_STATE, which stores the current OMAC state of the Machine.
In addition to the above variables, we must remember to map as inputs the commands of type ST_MACHINE_CMD representing start, stop, abort and etc…, highlighted in red in Fig.1.
As for the methods we have the init which deals with the initialization of the private/static variables of our Unit and the cycle method in which we are going to define the main state machine.
- Cycle implementation
A possible implementation of the cyclemethod is presented below. As can be seen, in each transition state (shown in green in Fig. 1), feedback from the units is evaluated to determine the transition to a stable state (shown in yellow). In the latter, the system waits for commands (Cmd) sent by the operator or programmer, as mentioned earlier.
One difference that might stand out when comparing Fig.1 with the OMAC state machine implementation concerns the handling of the Aborted > Clearing > Stopped > Resetting transition. By personal choice, I decided that at the end of the Aborted state, following a reset command, the system goes directly to the Resetting state, as shown in Fig.2. This choice is motivated by the fact that, in the Aborted state, I typically manage the automation shutdown, thus ensuring that the machine is in a safe condition. At this point, once the reset command is received, it is possible to perform all the necessary reset operations and go directly to the Idle state.

In addition, as Machine output, the aggregate variables InCycle, InStoppedand InAborted, also shown in Fig.1 and Fig.2, can be updated. These signals allow the current state of the machine to be effectively monitored.
def cycle(self):
if self.iCmd.Abort and not self.oInAborted:
self.__State.Value = 6
elif self.iCmd.Stop and not self.oInStopped and not self.oInAborted:
self.__State.Value = 4
match self.__State.Value:
case 0:
self.__State.Text = 'IDLE'
if self.iCmd.Start:
self.__State.Value = 1
case 1:
self.__State.Text = 'STARTING'
if self.iUnits_Fbks.Started:
self.__State.Value = 2
case 2:
self.__State.Text = 'EXECUTE'
if self.iCmd.Complete:
self.__State.Value = 12 # Completing
if self.iCmd.Hold:
self.__State.Value = 15 # Holding
if self.iCmd.Suspend:
self.__State.Value = 9 # Suspending
case 4:
self.__State.Text = 'STOPPING'
if self.iUnits_Fbks.Stopped:
self.__State.Value = 5
case 5:
self.__State.Text = 'STOPPED'
if self.iCmd.Reset and not self.iCmd.Stop:
self.__State.Value = 8 # resetting
case 6:
self.__State.Text = 'ABORTING'
if self.iUnits_Fbks.Aborted:
self.__State.Value = 7
case 7:
self.__State.Text = 'ABORTED'
if self.iCmd.Reset and not self.iCmd.Abort:
self.__State.Value = 8 # resetting
case 8:
self.__State.Text = 'RESETTING'
if self.iUnits_Fbks.Resetted:
self.__State.Value = 0 # idle
## Suspending
case 9:
self.__State.Text = 'SUSPENDING'
if self.iUnits_Fbks.Suspended:
self.__State.Value = 10
case 10:
self.__State.Text = 'SUSPENDED'
if self.iCmd.Start:
self.__State.Value = 11
case 11:
self.__State.Text = 'UNSUSPEND'
if self.iUnits_Fbks.Unspended:
self.__State.Value = 2 # Execute
## Complete
case 12:
self.__State.Text = 'COMPLETING'
if self.iUnits_Fbks.Completed:
self.__State.Value = 12
case 13:
self.__State.Text = 'COMPLETE'
if self.iCmd.Reset:
self.__State.Value = 8 # Resetting
## Hold
case 14:
self.__State.Text = 'HOLDING'
if self.iUnits_Fbks.Holded:
self.__State.Value = 14
case 15:
self.__State.Text = 'HELD'
if self.iCmd.Unhold:
self.__State.Value = 15
case 16:
self.__State.Text = 'UNHOLDING'
if self.iUnits_Fbks.Unholded:
self.__State.Value = 2 # Execute
# Output
self.oActual_State.Value = self.__State.Value
self.oActual_State.Text = self.__State.Text
self.oInCycle = (self.__State.Value == 0) or \
(self.__State.Value == 1) or \
(self.__State.Value == 2) or \
(self.__State.Value == 3) or \
(self.__State.Value == 9) or \
(self.__State.Value == 10) or \
(self.__State.Value == 11) or \
(self.__State.Value == 12) or \
(self.__State.Value == 13) or \
(self.__State.Value == 14) or \
(self.__State.Value == 15) or \
(self.__State.Value == 16)
self.oInStopped = (self.__State.Value == 4) or \
(self.__State.Value == 5)
self.oInAborted = (self.__State.Value == 6) or \
(self.__State.Value == 7)
Units

Each Unit must handle the following data structures:
- Requests: variable of type ST_REQUESTS, used as input to receive requests from the Machine. These requests mainly concern OMAC state changes.
- Fbks: variable of type ST_UNITS_FDKs, used to send feedback to the Machine.
Again, as methods we will have init and cycle. In cycle we are going to manage the Unit state machine.
- Cycle implementation
A possible implementation of the cyclemethod is presented below. As can be seen, the state of the unit follows the state of the Machine. Again, in each transition state Fbks are sent to the Machine. The latter are reset either in the corresponding finite state (e.g. self.oFdks.Stopped is set to True in Stopping while to False in Stopped) and in the Resetting state (so as soon as the Machine enters or InCycle).
def cycle(self):
self.__State.Value = self.iRequests.State
match self.__State.Value:
case 0:
self.oFdks.Resetted = False
self.__State.Text = 'IDLE'
case 1:
self.__State.Text = 'STARTING'
self.oFdks.Started = True
case 2:
self.oFdks.Started = False
self.__State.Text = 'EXECUTE'
case 3:
self.__State.Text = 'COMPLETE'
self.oFdks.Completed = True
case 4:
self.__State.Text = 'STOPPING'
self.oFdks.Stopped = True
case 5:
self.oFdks.Stopped = False
self.__State.Text = 'STOPPED'
case 6:
self.__State.Text = 'ABORTING'
self.oFdks.Aborted = True
case 7:
self.oFdks.Aborted = False
self.__State.Text = 'ABORTED'
case 8:
self.__State.Text = 'RESETTING'
self.oFdks.Started = False
self.oFdks.Suspended = False
self.oFdks.Unspended = False
self.oFdks.Holded = False
self.oFdks.Unholded = False
self.oFdks.Completed = False
self.oFdks.Stopped = False
self.oFdks.Resetted = True
##Suspending
case 9:
self.__State.Text = 'SUSPENDING'
self.oFdks.Suspended = True
case 10:
self.oFdks.Suspended = False
self.__State.Text = 'SUSPENDED'
case 11:
self.__State.Text = 'UNSUSPEND'
self.oFdks.Unspended = True
## Complete
case 12:
self.__State.Text = 'COMPLETING'
self.oFdks.Completed = True
case 13:
self.__State.Text = 'COMPLETE'
self.oFdks.Completed = False
## Hold
case 14:
self.__State.Text = 'HOLDING'
self.oFdks.Holded = True
case 15:
self.__State.Text = 'HELD'
self.oFdks.Holded = False
case 16:
self.__State.Text = 'UNHOLDING'
self.oFdks.Unholded = False
# Output
self.oActual_State.Value = self.__State.Value
self.oActual_State.Text = self.__State.Text
3.2 Exchange of signals between Units

At this point it is necessary to define a class that handles the exchange of signals between the Machine and its Units. This mechanism takes place within the Program, specifically in the methcyclemethod. Here two directions of communication will be handled:
- From the Units to the Machine:
- The feedback that the units return will be read.
- These feedbacks are evaluated with AND-type logic: for example, in order for the machine to exit the Abortingstate, it is necessary that all the units have successfully finished their Aborting phase by setting the variable .oFbks.Abortedto True.
2. From Machine to Units:
- The OMAC status to be executed will be sent
Obviously, we are also going to handle the Start, Stop, Abort, Reset and Suspend commands here. In particular, for the Stop and Abort commands, it is possible — as anticipated — to include in OR logic also any error conditions coming from the machine automation, such as faults in motors, valves, or other devices. In this way, the machine can react not only to manual commands given by the operator, but also in case of automatically detected faults.
def cycle(self, signals : IO_Signals):
#Cmd
#Set the commands to Machine
self.machine.iCmd.Start = signals.start
self.machine.iCmd.Stop = signals.stop #or sensor.error
self.machine.iCmd.Abort = signals.emergency #or motor.error
self.machine.iCmd.Reset = signals.reset
self.machine.iCmd.Suspend = signals.suspend
self.machine.iCmd.Complete = False #Conditions for complete
self.machine.iCmd.Hold = False #Conditions for Hold
self.machine.iCmd.Unhold = False #Conditions for Unhold
#Fbks
#From Units to Machine
self.machine.iUnits_Fbks.Started = self.unit1.oFdks.Started and self.unit2.oFdks.Started
self.machine.iUnits_Fbks.Aborted = self.unit1.oFdks.Aborted and self.unit2.oFdks.Aborted
self.machine.iUnits_Fbks.Stopped = self.unit1.oFdks.Stopped and self.unit2.oFdks.Stopped
self.machine.iUnits_Fbks.Resetted = self.unit1.oFdks.Resetted and self.unit2.oFdks.Resetted
self.machine.iUnits_Fbks.Completed = self.unit1.oFdks.Completed and self.unit2.oFdks.Completed
self.machine.iUnits_Fbks.Suspended = self.unit1.oFdks.Suspended and self.unit2.oFdks.Suspended
self.machine.iUnits_Fbks.Unspended = self.unit1.oFdks.Unspended and self.unit2.oFdks.Unspended
self.machine.iUnits_Fbks.Holded = self.unit1.oFdks.Holded and self.unit2.oFdks.Holded
self.machine.iUnits_Fbks.Unholded = self.unit1.oFdks.Unholded and self.unit2.oFdks.Unholded
self.machine.cycle()
#Units
#From Machine to Units
self.unit1.iRequests.State = self.machine.oActual_State.Value
self.unit1.cycle() #Invoke Cycle of unit
self.unit2.iRequests.State = self.machine.oActual_State.Value
self.unit2.cycle()
4. Conclusions
In conclusion, we have seen how to program a system according to the PackML standard, analyzing its most relevant aspects for industrial automation. PackML is distinguished by its ability to provide centralized supervision through SCADA (Supervisory Control and Data Acquisition) or HMI (Human-Machine Interface) systems.
By defining standardized states and transitions, the operational status of each machine can be collected and monitored in real time from a centralized point. This allows operators not only to check whether a machine is running, in alarm, or stopped, but also to take direct action to change its state, such as starting it, stopping it, or handling any errors.
Another key element is interoperability between machines from different manufacturers, made possible precisely by standardization. By adopting a common set of rules and behaviors, machines can be easily integrated into complex systems without the need for costly customizations. This simplifies integration and improves operational efficiency, making PackML a strategic solution for modern production line management.
References
[1] Omac.org
[2] PackML
PackML: towards smarter automation was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Giuseppe Pio Cannata

Giuseppe Pio Cannata | Sciencx (2025-05-19T02:45:51+00:00) PackML: towards smarter automation. Retrieved from https://www.scien.cx/2025/05/19/packml-towards-smarter-automation/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.