Design¶
This chapter discusses various decisions made in the design of the SDK.
Command model standard¶
Each of the FBs/FCs in this SDK follow a convention called the “Command model standard”. This convention is loosely based on the PLCopen design recommendations for motion control blocks.
Generally speaking, every FB/FC is designed to perform a particular command or set of commands. Their interfaces all follow the same conventions, so all blocks of a given model type are called in the same way.
There are three different command models:
Function model
Execute model
Enable model
The function model is for simple stateless operations (i.e. FCs), while the execute and enable models run over time and may have internal memory (i.e. FBs). These models will be covered in more detail below.
The state output parameter¶
Each command can be thought of as a state machine, where each instance of the command exists in exactly one state at any point in time. These states are represented by a UDT output parameter called state
. Within this UDT, there is a member state.code
that represents the current state as a 16-bit Word value.
The values for state.code
are standardized such that the most-significant byte corresponds to a particular category. These categories are defined in the following table:
State category |
State value (hex) |
Description |
---|---|---|
IDLE |
16#0xxx |
The command is not running (enable model only) |
BUSY |
16#1xxx |
The command is running (execute model only) |
DONE |
16#2xxx |
The command completed successfully (execute/function models only) |
VALID |
16#3xxx |
The command is running without issues/errors (enable model only) |
ABORTED |
16#4xxx |
The command was aborted (either locally or by a higher-priority command) |
ERROR |
16#8xxx |
An error occurred while processing the command |
ERROR_BUSY |
16#9xxx |
An error occurred, but the command is busy attempting to recover (enable model only) |
There can be multiple independent states within each category - for example, states 16#8000, 16#8001, and 16#8002 would represent distinct error conditions for that command.
The state
parameter also expresses the command’s state in the form of boolean flags, each corresponding to the bits in state.code
’s most significant byte. The flags are defined in the following table:
State flag |
bit number |
Description |
---|---|---|
BUSY |
0 (2#0001) |
The command is running |
DONE |
1 (2#0010) |
The command completed successfully (execute/function models only) |
VALID |
1 (2#0010) |
The command is running without issues/error (enable model only) |
ABORTED |
2 (2#0100) |
The command was aborted (either locally or by a higher-priority command) |
ERROR |
3 (2#1000) |
An error occurred while processing the command |
For example, if state.code
is set to 16#8000, the ERROR flag would be on and the rest would be off. In many cases, using the flags is more convenient than the state code value. More details regarding the state categories and flags for each of the command models will be covered in the next sections.
Function Model¶
The function model represents a simple, stateless operation that processes immediately. All FCs in the SDK follow this model, since FCs, by definition, have no internal memory. An example is DecodeDateTime - this command reads the source input parameters and computes a native datetime value.
Function model commands typically use only DONE and ERROR states. If something goes wrong during execution (e.g. a parameter is out of range), an error code can be returned to notify the caller. The state codes are normally passed through the return value of the FC, but can also be passed as an output parameter. If a command has no error conditions, then the state code can be omitted altogether.
Execute model¶

The execute model is used for commands that run from start to finish. Once started, the command runs until it either completes the operation (DONE), experiences an error (ERROR), or is aborted (ABORTED). This model is stateful; its behavior depends on previous state and can change over time.
Execute model commands are triggered using an execute
boolean input parameter. Each rising edge on this parameter executes the command once. The command only re-triggers once the command is in a “terminal” state - i.e., DONE, ERROR, or ABORTED. Triggering the command while already running does nothing.
Here is a timing diagram for the execute model:

A rising edge on execute starts the command, setting the busy flag to true. |
|
Execute is automatically cleared by the command. |
|
When the command completes, busy is set to false and done is set to true. |
|
When command is executed again, done is set to false and busy is set to true. |
|
Additional execute signals while busy are ignored, rather than restarting the command. |
|
If a problem occurs while processing the command, the error flag is set to true. |
|
Similarly, if something interrupts the command, the aborted flag is set to true. |
Note that the execute
input will automatically be cleared by the FB; there is no need to reset the trigger from outside logic. This provides additional flexibility, e.g. when controlling from browser-based HMIs that lack momentary push buttons. The input can still be written from a regular coil - either way, the command will behave the same.
Some commands (but not all) may include a cancel
input for stopping a command early. cancel
inputs work the same way as execute
, except they trigger a transition from BUSY to ABORTED.
Enable model¶

The enable model is used for commands that run indefintely. This differs from the execute model in that the enable model does not have a DONE condition. Instead, this model refers to normal operation as VALID. The enable model is useful for things like jog operations.
Enable model commands use an enable
boolean input to control their state. When enable
is false, the command is IDLE and does nothing. When true, the command runs its logic sequence and writes to its outputs.
An example of the enable model is the PumpControl FB. When enabled, the FB responds to inputs and updates the system mode accordingly. Disabling the FB allows it to be “detached” from the device’s registers, which could then be controlled through some other logic.
Here is a timing diagram for the enable model:

A rising edge on enable starts the command. The busy and valid flags are set to true. |
|
When an error occurs, error is set to true and valid is set to false. In this case, the block logic can automatically recover from the error, so the busy flag stays at true. |
|
The block logic handled the error - valid is set back to true and error is set back to false. |
|
In this case, an error occurred that requires user intervention. Error is set to true, and both valid and busy are set to false. |
|
Setting enable to false resets the block and clears the error. |
|
Setting enable to false also sets both valid and busy to false. |
|
The command may also be aborted, causing the aborted flag to be true and valid/busy to be false. |
Unlike the execute model, the state flags for the enable model are not all mutually-exclusive. Under normal operation, the VALID and BUSY flags are true, meaning the command is running without issues. If an issue does occur, there are two possibilities - either it is something the FB can deal with automatically, or it is something outside of its control.
In the former scenario, the command enters an ERROR_BUSY state, which sets both the ERROR and BUSY flags to true. This means the command has identified an issue and is working to resolve it, e.g. retrying a failed message.
In the latter scenario, the command enters an ERROR state, without the BUSY flag. The command has experienced an issue that it cannot fix, and it is up to the calling code to resolve it. When this happens, the command must be reset by setting enable
to false. It can then be re-enabled once the issue has been dealt with.
The ABORTED state works the same way as the ERROR state, but this is typically used for situations where another process in the program has overwritten some value the command was using. Because enable model commands can be disabled at any point, they do not need a cancel
input, like in the execute model.
Note that, while enable model commands are designed to be stopped at any time, there may be several cycles where the command performs some cleanup logic before returning to IDLE state.
Accessing Profinet device data¶

When using the Profinet protocol, the device data is accessed via memory addresses in the process image of the PLC. The addresses can be freely assigned and do not have to be in any particular order. Unfortunately, this makes writing modular code like the SDK library difficult. Our FBs/FCs need some way to locate the registers without hardcoding in the address values.
One approach would be to pass each of the required input addresses as parameters to the FB/FC, and to write the output addresses through output parameters. However, this approach becomes tedious when many different registers are needed. For example, the SendDCS FB needs access to all DCS registers - over 16 total. It’s also very easy for the programmer to mistakenly connect a register to the wrong parameter, resulting in bugs that are difficult to troubleshoot.
Another approach would be to use PLC tags assigned to the Profinet addresses. This is typical when writing a program for the end user. However, this means the code will depend on a specific set of tags being present in the PLC. Since the tags would be hardcoded into the logic, you would not be able to control multiple PD2Ks from the same PLC without first rewriting the code.
Fortunately, there is a built-in solution that provides easy, modular access to Profinet IO devices that does not involve rewriting any code!
Accessing a Profinet IO device by device number¶
Within a given Profinet network, every device is assigned a unique device number. This number can be found in its device configuration under General -> Profinet Interface -> Ethernet addresses -> Profinet -> Device number:

Each Profinet device has “slots” for each of its data registers. These slot numbers are fixed for every PD2K system and defined in the GSDML file (e.g. “current system mode” will always be slot 1). Together, the device number and slot number define a “geographic location” for a given piece of data.
Siemens provides a built-in way to look up the memory address of a slot from its geographic location. The solution is using the GEO2LOG FC (for S7-300/400, the equivalent is GEO_LOG). By passing a device number and slot number to this FC, we can look up the slot’s corresponding memory location and access it like any other address. Since all Graco products have fixed slot numbers, we can access all device data from a FB/FC just by passing in the device number - no tags required!
This SDK provides two FCs called GetDataBySlotNum
and SetDataBySlotNum
. These are wrappers around GEO2LOG/GEO_LOG, and they are used to read/write data from a Profinet device. The device number is passed to the pnDeviceNum
input parameter, and the desired slot number is passed to slotNum
. The other FBs/FCs use these FCs internally to access the device registers by their given slot numbers.
Using TypeTarget¶
The TypeTarget UDT is used extensively throughout the SDK code. This type has a member named “pnDeviceNum” should be assigned a specific PD2K Profinet device number. The SDK code uses this value to access device data through the method described above.
Note
TypeTarget also supports running in “simulation” mode, where instead of operating on a real Profinet device, all data flows through a “simData” array member in the UDT. This can be useful for testing logic when a real PD2K is not available. Setting the “simulate” member to true enables this mode. Just make sure to set this to “false” when in production, otherwise the system will not operate correctly!