DISCLAIMER: This project is not intended for everyday use and made available without any support. However, we welcome any kind of feedback via the issue tracker or by e-mail.
The Material Flow (MF)-Plugin is a plugin for the Production Flow Description Language (PFDL) Domain Specific Language. It enables the user to describe advanced transportation tasks included in a material flow. Such a material flow consists of 1..N transport orders. A transport order represents an operation of a pickup of a good at location A and a delivery of the good at a dedicated location B. In general, a transport order can be executed by a human, by an automated guided vehicle (AGV), an autonomous mobile robot (AMR), or in collaborative mode. In the following, the executor/executing entity will be addressed in general with mobile robot (MR).
This repository contains the grammar specification for the MF-Plugin and provides an own scheduler that supports the extended syntax. The grammar is realized with the help of ANTLR.
- MF-Plugin grammar extension
- Installation
- Generate ANTLR files from the grammar
- Unit and Integrationtests
- Release Notes
- License
The MF-Plugin extends the PFDL to enable the definition of transports in a declarative way. It contains
- New predefined Structs to describe Intralogistic primitives
- Additional Options to describe a Task
- Orders and Ordersteps to describe Transports
- Rules to express complex logic in Tasks or OrderSteps
All of the additionally defined grammar contained in the MF-Plugin can be used aside the grammar and functionalitites the PFDL defines. Therefore, the MF-Plugin comes with an own Scheduler, which expands the PFDL Scheduler by adding some new functionalities and editing/overwriting some others. A mainTask
is always required to contain the main program (like the productionTask
for pure PFDL programs).
The MF-Plugin contains of predefined structs describing intralogistic primitives, that are essential in the domain of intralogistics. Currently three primitves exist in the context of the MF-Plugin:
Note that each created Struct in the context of the MF-Plugin will automatically have an id
and time
parameter assigned that can be accessed, e.g., for comparisons. Therefore each Struct will have the following structure:
Struct <Struct name>
id: string
time: number
<additional attribute definitions>
End
For simplicity, the two base attributes are not shown in the following Struct definitions and can be left out when defining own Struct definitions.
A transport is a movement of goods placed in a container (or bin) from one location to another location. In the MF-Plugin, a Location is defined as follows:
Struct Location
type: ""
End
Inside a program using the MF-PLugin syntax, an instance of Location can directly be used without declaring the Struct manually. See an example below. Note that the field id
is not required and will be set automatically if no value is passed (as time
as well).
Location goodsPallet
id: "productionArea_palletPlace"
type: "pallet"
End
Location warehousePos1
id: "warehouseArea_pos1"
type: "pallet"
End
Figure 1 illustrates this Scenario.
Figure 1: Production hall with two Locations.Sometimes it is necessary to determine the flow control, for example, when an action depends on an interaction with an user or a physical sensor. The primitive Struct Event is introduced to represent those dependencies, which is defined as follows:
Struct Event
value: boolean | number | string
End
The value
represents the event status and can be either a boolean, a number or a string. See an example of an Event manualLoadingConfirmation
below. The Event's value
here might be set to true
after a button was pressed to confirm that a mobile robot has been loaded.
Event manualLoadingConfirmation
id : "buttonMRIsLoaded"
value : false
End
The primitive Time is linked to the UNIX cron job time. This primitive enables the scheduling of a Task in the future.
Struct Time
timing: string # Unix crontab Format
value: bool # is set to true if the time in timing was reached
End
A Task's time of execution can, for example, be set to 6:30am by creating a Time instance like startOfProduction
in the example below and passing it to the Task. The value
of startOfProduction
will be set to true
when the wanted timestamp is reached.
Time startOfProduction
timing: "30 6 * * *"
value: false
End
With the MF-Plugin, the Task functionality is expanded. Beneath Tasks and Services, any number of Order statements can be called from inside a Task. With the help of Orders, a Task in the MF-Plugin is able to describe that an item should be picked up at some location and be delivered to another location.
The example below builds upon the two Locations which were defined through goodsPallet
and warehousePos1
. To transport a good from goodsPallet
to warehousePos1
, two TransportOrderSteps, which define the corresponding location, are needed. Each TransportOrderStep is then included in the Task’s Transport statement. See Figure 2 for an illustration of the MF-Plugin code.
TransportOrderStep loadGoodsPallet
Location: goodsPallet
End
TransportOrderStep unloadGoodsPallet
Location: warehousePos1
End
Task transportGoodsPallet
Transport
From loadGoodsPallet
To unloadGoodsPallet
End
There are also some additional options that can be defined once in a Task. These statements are:
- StartedBy
- FinishedBy
- Constraints
The StartedBy and FinishedBy statements are able to restrict the start or finish of a Task, respectively, depending on a passed condition. This could either be the truth value of a Time instance or an expression that might be realized by a Rule. Note that the mainTask
ignores these statements because it is meant to be wrapped around the whole program.
See an example of a Task taskWithEvent
that contains a StartedBy statement that is linked to the Event manualLoadingConfirmation
described here. The Task is started after the condition was met, which happens by pressing the button connected to the Event. An illustration of this logic can be found in Figure 3.
Task taskWithEvent
StartedBy manualLoadingConfirmation.value
Transport
From loadGoodsPallet
To unloadGoodsPallet
End
Transports that need to fulfill certain constraints can be defined with the help of the Constraint keyword. In the following example, a time span is defined using the JSON inline syntax. Here the transport should take place in the period from 14:05 to 14:45pm. This restriction is taken into account during the order assignment, either by the process itself, or by the MRs that apply for the order.
Task transportGoodsPallet
Transport
From loadGoodsPallet
To unloadGoodsPallet
Constraints: {"TransportStart" : "05 14 * * *", "TransportFinished" : "45 14 * * *"}
End
In the simplest form, a Task in the MF-Plugin describes that an item should be picked up at some location and be delivered to another location. Furthermore, actions can be executed at specified locations. There are three additional operations available in a Task to tell a mobile robot what to do: Transport, Move and Action.
A Transport Order (indicated by the Transport keyword) is made up of at least two TransportOrderSteps. A TransportOrderStep inherently consists of a Move Order or an Action Order or both. A Move Order is defined as the movement from the current location to a specific destination. An Action Order is defined as an action of manual or automatic loading or unloading of a mobile robot.
To allow more flexibility, besides the Transport, there also exist a Move and Action statement, representing the Move and Action Order. However, these statements can only appear in a Task after a Transport was executed. Otherwise, for example with only an Action Order in a Task, it would make no sense that a random MR executes an action, as this Action in general depends on a specific Location, which might not be similar to the MR's location. Instead, each Transport, Action or Move included in a Task is meant to be executed by the same mobile robot. To provide coherence, a MoveOrderStep and ActionOrderStep is included, which works like the TransportOrderStep.
A Transport Order is indicated by the Transport keyword and consists of at least one TransportOrderStep describing the pickup Location and exactly one TransportOrderStep for the delivery Location. Additional options are also possible for one TransportOrderStep, as explained here. For the pickup indicated by the From keyword, multiple TransportOrderSteps are also possible. The syntax looks like the following:
Transport
From {transportOrderStepPickup1, ..., transportOrderStepPickupN}
To {transportOrderStepDelivery}
See an example of a Task transportGoodsPallet
describing a Transport Order containing two pickup TransportOrderSteps below:
Location goodsPallet2
id: "productionArea_palletPlace"
type: "pallet"
End
TransportOrderStep loadGoodsPallet
Location: goodsPallet
End
TransportOrderStep loadgoodsPallet2
Location: goodsPallet2
End
TransportOrderStep unloadGoodsPallet
Location: warehousePos1
End
Task transportGoodsPallet
Transport
From loadGoodsPallet, loadgoodsPallet2
To unloadGoodsPallet
End
Note, that the order of the pallets inside the From statement is not necessarily the pickup order. The solution of this problem is solved internally, by a so called execution engine. It is only important to define the pickup locations, respectively where the goods should be transported to and not how it is done. In the example scenario shown in Figure 4, goodsPallet2
is visited before goodsPallet
by the MR.
A Move Order is called by the Move keyword and takes only one MoveOrderStep to set the Location for the mobile robot to drive towards.
Move
To {moveOrderStep}
As mentioned, a Move Order can only appear in a Task if there was at least one Transport Order (and therefore a MR assigned) before. See an example below. Here, the mobile robot that executes the transport in transportGoodsPallet
should drive to a specific parking location afterwards. The following program is illustrated in Figure 5.
Location parkingPos
id: "parkingPos_1"
End
MoveOrderStep moveToParkingPos
Location: parkingPos
End
Task transportGoodsPallet
Transport
From loadGoodsPallet
To unloadGoodsPallet
Move
To moveToParkingPos
End
An Action Order can be used to specify an Action that the MR should perform at its current position. An ActionOrderStep is necessary for that.
Action
Do {actionOrderStep}
As for the Move Order, an Action Order can only appear in a Task if there was at least one Transport Order (and therefore a MR assigned) before. In the example below, the MR is instructed to load a specific pallet at the Location of unloadGoodsPallet
by using an Action statement.
ActionOrderStep loadAdditionalGood
Parameters: {"load": "pallet2"}
End
Task transportGoodsPallet
Transport
From loadGoodsPallet
To unloadGoodsPallet
Action
Do loadAdditionalGood
End
OrderSteps are used to specify the Order statements Transport, Move and Action. For each of them, there exists an OrderStep with different options for the specific usecase. Additionally, each OrderStep type can also optionally contain a StartedBy and FinishedBy statement, which work as for a Task. Furthermore, an OnDone statement can be defined once per OrderStep. This statement takes a Task name. The corresponding Task is executed when the OrderStep containing the OnDone statement finishes. With that use, more complex material flows can be realized. Note that the MR that is assigned to a possible followUp Task defined in the OnDone statement does not have to be the same as the one that executes the original Task.
A TransportOrderStep (TOS) is mainly described by a Location (representation of a Move Order) and optionally the action to be performed there. Here, the optional statement Parameters is used to define or specify the execution of an action. For example, this action could be either loading at a source or unloading at a sink. This corresponds to an Action Order, which is the execution of a specific action at the current position.
TransportOrderStep {name}
# required
Location: {Instance_Location}
# optional
Parameters: {json_object, expression}
StartedBy: {Rule(...), expression}
FinishedBy: {Rule(...), expression}
OnDone: {Task} # follow up task
End
The following example Task transportgoodsPallet2
consists of two TransportOrderSteps. The TransportOrderStep loadGoodsPallet
defines a Location (required) and an optional FinishedBy condition that has to be fullfilled before the Task can be continued. unloadGoodsPallet
contains an optional Parameters statement to specify the exact pallet where to unload the goods, here the second one.
Event mrLoaded
value: false
End
TransportOrderStep loadGoodsPallet
Location: goodsPallet
FinishedBy: mrLoaded.value
End
TransportOrderStep unloadGoodsPallet
Location: warehousePos1
Parameters: {"palletNumber" : 2}
End
Task transportgoodsPallet2
Transport
From loadGoodsPallet
To unloadGoodsPallet
End
The scenario is depicted in Figure 6.
Figure 6: A Transport with two TransportOrderSteps, one with a FinishedBy option and one with a Parameters statement to explicitly define the pallet where to unload the goods.The MoveOrderStep and ActionOrderStep are defined similarly to the TransportOrderStep. The only difference is, that a MoveOrderStep do not contain a Parameter statement and the ActionOrderStep do not contain a Location statement and needs to specify a Parameter statement instead. See the definitions below.
MoveOrderStep {name}
# required
Location: {Instance_Location}
# optional
StartedBy: {Rule(...), expression}
FinishedBy: {Rule(...), expression}
OnDone: {Task} # follow up task
End
ActionOrderStep {name}
# required
Parameters: {json_object, expression}
# optional
StartedBy: {Rule(...), expression}
FinishedBy: {Rule(...), expression}
OnDone: {Task} # follow up task
End
With the help of Rules, the values of instances can be evaluated in a more advanced manner. It enables an event-driven program control with more comprehensive expressions. Within Rules, the logical operators for comparisons (<, ≤, ==, !=, ≥, >) and concatenation (AND, OR, NOT(represented by !)), as well as the mathematical basic arithmetic operations (+, -, ∗, /) can be used. Dependencies, sequences or even temporal properties can be realized by using Rules. Expressions in Rules are only evaluated, so the values are not changed. If all expressions within a rule evaluate to true
the value true
is returned, otherwise this rule is invalid and false
is returned. Moreover, a Rule call can also consist of other Rules.
Parameters or default values can be passed into a Rule. The following example has the three parameters event1
, event2
and offset
. The first two parameters have to be passed at runtime while the third, if it is not assigned a value manually, is assigned a default value (here 0).
Rule beforeWithOffset(event1, event2, offset = 0)
(event1.time + offset < event2.time) And (event1.value == 5)
End
Note that, if there are several expressions in multiple lines, it is implicitly assumed that all expressions of the Rule are linked with an AND. The Rule beforeWithOffset2
defined in the following example is equivalent to the previously defined Rule beforeWithOffset
.
Rule beforeWithOffset2(event1, event2, offset = 0)
event1.time + offset < event2.time
event1.value == 5
End
As this plugin is specified to complement the PFDL, the code needs to be inside the plugins
directory of the PFDL Scheduler. The MF-Plugin scheduler can then be accessed via the following commands:
import pfdl_scheduler.plugins as plugins
mf_plugin_scheduler = plugins.load("mf_plugin.mf_plugin.scheduler", "pfdl_scheduler")
You can find a full demonstrator of the MF-Plugin Scheduler here. Note that this file has to be placed on top of the project structure (of the PFDL repository) in order to work as expected. Please also have a look at the several example files that use the MF-Plugin grammar!
All requirements are listed in requirements.txt and can be installed via pip install -r requirements.txt
.
After changing the MF-Plugin grammar, you will need to regenerate the respective generated ANTLR classes (Lexer, Parser, Visitor and Listener) from the grammar specification:
Make sure to first install ANTLR correctly. Follow the guide on the official ANTLR site to install it.
To generate the Lexer and Parser with an additional visitor as Python classes, run the following command inside the grammar
directory:
antlr4 -v 4.9.3 -Dlanguage=Python3 -visitor PFDLLexer.g4 PFDLParser.g4
Now, the generated classes can be used within a Python script. Within the original project structure, it is necessary to move/copy the generated files into the mf_plugin/parser
directory.
The project uses a pipeline to run different unit and integration tests. If you want to execute the tests locally, run it via VSCode (Python Extension) or via the following command in the projects root directory (in the PFDL directory):
python3 -m unittest discover -v
This will give a detailed overview over all test results. If you want to run the test isolated use something like the following:
python3 -m unittest discover -v pfdl_scheduler/plugins/mf_plugin/tests
Material Flow (MF)-Plugin is licensed under the MIT License. See LICENSE for details on the licensing terms.
If you use the MF-Plugin for research, please include the following reference in any resulting publication.
@INPROCEEDINGS{10710795,
author={Gödeke, Jana and Horstrup, Maximilian and Detzner, Peter},
booktitle={2024 IEEE 29th International Conference on Emerging Technologies and Factory Automation (ETFA)},
title={Towards a Unified Flow Description Language for CPPSs: An Example with Material Flows},
year={2024},
volume={},
number={},
pages={1-8},
keywords={Concurrent computing;Production systems;DSL;Synchronization;Manufacturing automation;Domain specific languages;Convergence;Logistics;Cyber-Physical Production System;Domain-Specific Language},
doi={10.1109/ETFA61755.2024.10710795}}