Design
######

This chapter discusses various decisions in designing the SDK.

Command model standard
**********************
Each of the AOIs 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 <https://plcopen.org/technical-activities/motion-control>`_.

Generally speaking, every AOI 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, while the execute and enable models run over time and may depend on their internal memory. 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 an output parameter ``state``. The values range between 16#0000 and 16#FFFF (i.e., only the lower 16-bits are used, the rest are ignored).

The state values are standardized such that the most-significant byte corresponds to a particular category. These categories are defined in the following table:

.. list-table:: 
    :widths: 20 20 60
    :header-rows: 1

    * - 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 command's state is also expressed in the form of boolean output parameters, each corresponding to the bits in ``state``'s most significant byte. The flags are defined in the following table:

.. list-table:: 
    :widths: 15 15 70
    :header-rows: 1

    * - 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`` 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 following sections.

Function Model
==============
The function model represents a simple, stateless operation that processes immediately. 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. If a command has no error conditions, then the state code can be omitted altogether.

Execute model
=============

\ 

.. image:: ../assets/executeModelDiagram.drawio.png

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:

.. image:: ../assets/executeCmdTiming.png

.. list-table:: 
    :widths: 20 80   
    :header-rows: 0
    
    * - a.
      - A rising edge on execute starts the command, setting the busy flag to true.
    * - b.
      - Execute is automatically cleared by the command.
    * - c.
      - When the command completes, busy is set to false and done is set to true.
    * - d.   
      - When command is executed again, done is set to false and busy is set to true.
    * - e.
      - Additional execute signals while busy are ignored, rather than restarting the command.  
    * - f.
      - If a problem occurs while processing the command, the error flag is set to true.
    * - g.
      - Similarly, if something interrupts the command, the aborted flag is set to true.

Note that the ``execute`` input will automatically be cleared by the AOI; 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
============

\ 

.. image:: ../assets/enableModelDiagram.png

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 MainControl AOI. When enabled, the AOI responds to inputs and updates the system mode accordingly. Disabling the AOI 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:

.. image:: ../assets/enableCmdTiming.png

.. list-table:: 
    :widths: 20 80   
    :header-rows: 0
    
    * - a.
      - A rising edge on enable starts the command. The busy and valid flags are set to true.
    * - b.
      - 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.
    * - c.
      - The block logic handled the error - valid is set back to true and error is set back to false.
    * - d.   
      - In this case, an error occurred that requires user intervention. Error is set to true, and both valid and busy are set to false.
    * - e.
      - Setting enable to false resets the block and clears the error.  
    * - f.
      - Setting enable to false also sets both valid and busy to false.
    * - g.
      - 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 AOI 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.

Sharing access to device registers
**********************************
Many of the AOIs in this SDK write to the same sets of Ethernet/IP module data. For example, The ReadAlarmInfo and ReadRecipe AOIs both call SendDCS internally, which uses the DCS registers to query data from the PD2K. Only one DCS request can be sent at a time, so care must be taken to avoid writing the registers from multiple locations in logic at the same time. For example, if ReadAlarmInfo and ReadRecipe were both executed at the same time and allowed to write to the DCS registers, they would interfere with one another and result in a race condition.

To avoid this, each of the AOIs in this SDK are designed using a concept known as a semaphore. In general, semaphores prevent simultaneous writes to a shared resource (like the device registers) using a locked/unlocked mechanism. In short, the AOIs will take turns without interfering with one another.

Take the example of executing both the ReadAlarmInfo and ReadRecipe AOIs at the same time. Before either AOI does anything, they will each read the DCS acknowledge and DCS command register values. If both these are 0 (i.e. no operation, or NOP), then that AOI can assume no other AOI is currently using the DCS registers and thus, continue its sequence. If either register is non-zero, however, then some other AOI has accessed it first, so the AOI will go into a wait state until either the registers become available again or the AOI times out.

Note that this mechanism only works because all AOIs follow the same rules of only accessing registers when they are considered unlocked. Nothing prevents a programmer from writing to a register directly, and doing so will likely conflict with the SDK AOIs.

It's also worth noting that this coordinating behavior works best with only 2-3 active AOIs at a time. If many blocks are waiting, It's possible some of them will never be called because the earlier blocks will always take control first. For this reason, the programmer may still need to execute these in a controlled sequence.

Accessing module data
******************************
Nearly all the AOIs are designed to operate directly on a PD2K module's data. These AOIs use two InOut parameters, ``moduleStatus`` and ``moduleCmds``. The former should be passed the tag with data coming from the PD2K, while the latter should be passed tag with data going out to the PD2K. For example, given a module named ``GRACO_PD2K``, the ``moduleStatus`` parameter should be passed ``GRACO_PD2K:I.Data``, and the ``moduleCmds`` parameter should be passed ``GRACO_PD2K:O.Data``. Note that the ``.Data`` subtag is used, which is an array of DINTs. This allows one to use the AOIs with dummy tags not associated with a real device, e.g. for simulations/testing.