8.2.3. How to Write State Machines

This section demonstrates how state machines are implemented within the foxBMS 2 project. A simple, but fully functional, real-world implementation of this can be found in the debug measurement IC driver (see Debug Default).

8.2.3.1. The Example

This example implements a simple state machine with the following states:

  • Uninitialized

  • Initialization

  • Running

  • Error

An error in this example is an unrecoverable error. This gives the state flow diagram in Fig. 8.1.

# Copyright (c) 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# We kindly request you to use one or more of the following phrases to refer to
# foxBMS in your hardware, software, documentation or advertising materials:
#
# - "This product uses parts of foxBMS®"
# - "This product includes parts of foxBMS®"
# - "This product is derived from foxBMS®"

digraph fsm_states {
    rankdir=LR;
    size="8,5"
    node [shape = doublecircle] nd_uninitialized nd_error;
    node [shape = circle] nd_initialization nd_running;

    nd_uninitialized        [label=<<B>Uninitialized</B>>];
    nd_error                [label=<<B>Error</B>>];
    nd_initialization       [label=<<B>Initialization</B>>];
    nd_running              [label=<<B>Running</B>>];

    nd_uninitialized ->nd_initialization    [label = "Initialize"];
    nd_initialization -> nd_running         [label = ""];
    nd_running -> nd_error                  [label = "Runtime error"];
    nd_running -> nd_running                [label = "Operational mode"];
    nd_initialization -> nd_error           [label = "Initialization error"];
}

Fig. 8.1 States and their transitions

The state Initialization consists of three substates, which are processed sequentially:

  • I0: The first initialization substate

  • I1: The second initialization substates

  • Iexit: The last initialization substate (e.g., for some cleanup)

The state Running consists of three substate which are run in an endless loop in the following order:

  • R0: The first running substate

  • R1: The second running substate

  • R2: The third running substate

In any of the states Initialization and Running and any of the substates errors can occur. If this is the case, the state machine transitions from the substate to the state Error. The full state machine graph is shown in Fig. 8.2 and in a simplified way in Fig. 8.3:

# Copyright (c) 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# We kindly request you to use one or more of the following phrases to refer to
# foxBMS in your hardware, software, documentation or advertising materials:
#
# - "This product uses parts of foxBMS®"
# - "This product includes parts of foxBMS®"
# - "This product is derived from foxBMS®"

digraph fsm_complete {
    rankdir=LR;
    size="8,5"
    node [shape = doublecircle]     nd_uninitialized
                                    nd_error;
    node [shape = circle]           nd_initialization_0
                                    nd_initialization_1
                                    nd_initialization_exit
                                    nd_running_0
                                    nd_running_1
                                    nd_running_2;

    nd_uninitialized        [label=<<B>Uninitialized</B>>];
    nd_initialization_0     [label=<<B>I</B><SUB>0</SUB>>];
    nd_initialization_1     [label=<<B>I</B><SUB>1</SUB>>];
    nd_initialization_exit  [label=<<B>I</B><SUB>exit</SUB>>];
    nd_running_0            [label=<<B>R</B><SUB>0</SUB>>];
    nd_running_1            [label=<<B>R</B><SUB>1</SUB>>];
    nd_running_2            [label=<<B>R</B><SUB>2</SUB>>];
    nd_error                [label=<<B>Error</B>>];

    nd_uninitialized -> nd_initialization_0
    nd_initialization_0 -> nd_initialization_1
    nd_initialization_1 -> nd_initialization_exit
    nd_initialization_exit -> nd_running_0
    nd_running_0 -> nd_running_1
    nd_running_1 -> nd_running_2
    nd_running_2 -> nd_running_0

    nd_initialization_0 -> nd_error
    nd_initialization_1 -> nd_error
    nd_initialization_exit -> nd_error
    nd_running_0 -> nd_error
    nd_running_1 -> nd_error
    nd_running_2 -> nd_error

}

Fig. 8.2 States and substates and their transitions

# Copyright (c) 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# We kindly request you to use one or more of the following phrases to refer to
# foxBMS in your hardware, software, documentation or advertising materials:
#
# - "This product uses parts of foxBMS®"
# - "This product includes parts of foxBMS®"
# - "This product is derived from foxBMS®"

digraph fsm_complete_simple {
    rankdir=LR;
    size="8,5"
    compound=true;
    node [shape = doublecircle]     nd_uninitialized
                                    nd_error;
    node [shape = circle]           nd_initialization_0
                                    nd_initialization_1
                                    nd_initialization_exit
                                    nd_running_0
                                    nd_running_1
                                    nd_running_2;

    nd_uninitialized                [label=<<B>Uninitialized</B>>];
    nd_error                        [label=<<B>Error</B>>];

    subgraph cluster_initialization {
        label = <<B>Initialization</B>>;
        nd_initialization_0            [label=<<B>I</B><SUB>0</SUB>>];
        nd_initialization_1            [label=<<B>I</B><SUB>1</SUB>>];
        nd_initialization_exit         [label=<<B>I</B><SUB>exit</SUB>>];
    }

    subgraph cluster_running {
        label = <<B>Running</B>>;
        nd_running_0                   [label=<<B>R</B><SUB>0</SUB>>];
        nd_running_1                   [label=<<B>R</B><SUB>1</SUB>>];
        nd_running_2                   [label=<<B>R</B><SUB>2</SUB>>];
    }

    nd_initialization_1 -> nd_error [ltail=cluster_initialization];
    nd_running_1 -> nd_error [ltail=cluster_running];
    nd_uninitialized -> nd_initialization_0
    nd_initialization_0 -> nd_initialization_1
    nd_initialization_1 -> nd_initialization_exit
    nd_initialization_exit -> nd_running_0
    nd_running_0 -> nd_running_1
    nd_running_1 -> nd_running_2
    nd_running_2 -> nd_running_0
}

Fig. 8.3 States and substates and their transitions in a simplified graph

8.2.3.2. Implementing the State Machine

The following describes the idea behind the state machine pattern and how it is implemented for the described example.

This how to is written in a top-down approach, starting for an abstract state machine interface to more detailed implementations of subfunctions. This makes the global understanding simpler. But this also means, that functions are used and only explained at a later point in the text.

Note

In this example, the module prefix will be EG. Sometimes in this how to it will a appropriate to use a variable reference for the module prefix. In that case {MODULE_PREFIX} is used.

Note

In running text functions always use parentheses with no argument or three dots (...) to indicate that a function is referred to. Code examples of course always implement the full and correct function or function call. Below two simple examples are shown:

  • The function void noArguments() is referred to by noArguments().

  • The function uint8_t addTwoNumbers(uint8_t a, uint8_t b) is referred to by addTwoNumbers().

8.2.3.2.1. Basics

All states MUST be put into an enum describing the states. There are four states in the example (Uninitialized, Initialization, Running, Error) plus the boilerplate of the state machine (a dummy state called Dummy and a state indicating that the state machine has never run called Has_never_run). The enum entries MUST use FSM_STATE as infix after the module prefix. Taking all these rules into account, the enum for the states used in this example looks like this:

Listing 8.12 Enumeration of the states
1
2
3
4
5
6
7
8
 typedef enum EG_FSM_STATES {
     EG_FSM_STATE_DUMMY,          /*!< dummy state - always the first state */
     EG_FSM_STATE_HAS_NEVER_RUN,  /*!< never run state - always the second state */
     EG_FSM_STATE_UNINITIALIZED,  /*!< uninitialized state */
     EG_FSM_STATE_INITIALIZATION, /*!< initializing the state machine */
     EG_FSM_STATE_RUNNING,        /*!< operational mode of the state machine  */
     EG_FSM_STATE_ERROR,          /*!< state for error processing  */
 } EG_FSM_STATES_e;

A similar pattern has to be applied for the substates. For the boilerplate, a dummy substate called Dummy (as in the state) and an additional substate called Entry have to be defined. The enum entries MUST use FSM_SUBSTATE as infix after the module prefix. Taking all these rules into account, the enum for the substates used in this example looks like this:

Listing 8.13 Enumeration of the substates
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 typedef enum EG_FSM_SUBSTATES {
     EG_FSM_SUBSTATE_DUMMY,               /*!< dummy state - always the first substate */
     EG_FSM_SUBSTATE_ENTRY,               /*!< entry state - always the second substate */
     EG_FSM_SUBSTATE_INITIALIZATION_0,    /*!< fist initialization substate */
     EG_FSM_SUBSTATE_INITIALIZATION_1,    /*!< second initialization substate */
     EG_FSM_SUBSTATE_INITIALIZATION_EXIT, /*!< last initialization substate */
     EG_FSM_SUBSTATE_RUNNING_0,           /*!< fist running substate */
     EG_FSM_SUBSTATE_RUNNING_1,           /*!< second running substate */
     EG_FSM_SUBSTATE_RUNNING_2,           /*!< third running substate */
 } EG_FSM_SUBSTATES_e;

A struct named {MODULE_PREFIX}_STATE_s contains the general state of the state machine, with variables like currentState and previousState. In this example this struct is named EG_STATE_s. This struct is typically extended by an additional struct that holds relevant information or data (EG_INFORMATION_s information). In a real application these are usually pointers to some database entries required (see Debug Default) or variables used within the module. In this example it is just a struct holding three values.

Listing 8.14 The state type
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 typedef struct EG_STATE {
     uint16_t timer;                      /*!< timer of the state */
     uint8_t triggerEntry;                /*!< trigger entry of the state */
     EG_FSM_STATES_e nextState;           /*!< next state of the FSM */
     EG_FSM_STATES_e currentState;        /*!< current state of the FSM */
     EG_FSM_STATES_e previousState;       /*!< previous state of the FSM */
     EG_FSM_SUBSTATES_e nextSubstate;     /*!< next substate of the FSM */
     EG_FSM_SUBSTATES_e currentSubstate;  /*!< current substate of the FSM */
     EG_FSM_SUBSTATES_e previousSubstate; /*!< previous substate of the FSM */
     EG_INFORMATION_s information;        /*!< Some information to be stored */
 } EG_STATE_s;

With these lines of code, all types needed for the state machine are defined. The next step is the implementation of the state machine.

The first thing to do is to declare a variable for the state machine state

Listing 8.15 The state variable
1
 extern EG_STATE_s eg_state;

and initialize it as shown in Listing 8.16. The members of the struct related to the state (previousState, currentState and nextState) MUST be initialized with EG_FSM_STATE_HAS_NEVER_RUN to indicate that the state machine has not run yet. The members of the struct related to the substate (previousSubstate, currentSubstate and nextSubstate) MUST be initialized with the dummy state EG_FSM_SUBSTATE_DUMMY. The information struct can be anything that is required by the application.

Listing 8.16 The state variable
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 EG_STATE_s eg_state = {
     .timer             = 0,
     .triggerEntry      = 0,
     .nextState        = EG_FSM_STATE_HAS_NEVER_RUN,
     .currentState     = EG_FSM_STATE_HAS_NEVER_RUN,
     .previousState    = EG_FSM_STATE_HAS_NEVER_RUN,
     .nextSubstate     = EG_FSM_SUBSTATE_DUMMY,
     .currentSubstate  = EG_FSM_SUBSTATE_DUMMY,
     .previousSubstate = EG_FSM_SUBSTATE_DUMMY,
     .information.r0   = 0,
     .information.r1   = 0,
     .information.r2   = 0,
 };

A state machine always consists of a periodic trigger function. The trigger function gets the state variable introduced above (eg_state in this example) as parameter. The trigger function MUST use Trigger as function name infix. This example uses EG_Trigger(). If needed, the name can be extended (e.g., EG_TriggerMeasurementIc()).

Listing 8.17 The trigger function
1
 extern EG_Trigger(EG_STATE_s *pEgState)

The trigger function is then called somewhere in the application with EG_Trigger(&eg_state);

The trigger function is always implemented as shown in Listing 8.18 where EG_RunStateMachine() is the actual state machine implementation. The base name of the function MUST be {MODULE_PREFIX}_RunStateMachine. The implementation of EG_CheckMultipleCalls() can be taken directly from the example code. The detailed explanation of this function is found later in the text in Section 8.2.3.3.1.

It is often necessary to wait a definite amount of time. This can be the case for example when the state machine waits for a measurement to be finished before continuing. Waiting is implemented via the variable timer which is a member of the state variable. It must be decremented one time every time the trigger function is called. Two cases can happen:

  • If it has the value zero, it stays at zero and the content of the state machine is processed further.

  • If is has a non-zero value, it is decremented and the trigger function exits without processing the state machine.

To wait a definite amount of time, the time variable must only be assigned a non-zero value. The time to wait will depend on the periodicity with which the state machine is processed via the trigger function. If timer is set to N and the trigger function is called with a period T, the wait time before the state machine is processed further will be N*T.

Listing 8.18 Implementation of the trigger function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 extern STD_RETURN_TYPE_e EG_Trigger(EG_STATE_s *pEgState) {
     FAS_ASSERT(pEgState != NULL_PTR);
     bool earlyExit                = false;
     STD_RETURN_TYPE_e returnValue = STD_OK;

     /* Check re-entrance of function */
     if (EG_MULTIPLE_CALLS_YES == EG_CheckMultipleCalls(pEgState)) {
         returnValue = STD_NOT_OK;
         earlyExit   = true;
     }

     if (earlyExit == false) {
         if (pEgState->timer > 0u) {
             if ((--pEgState->timer) > 0u) {
                 pEgState->triggerEntry--;
                 returnValue = STD_OK;
                 earlyExit   = true;
             }
         }
     }

     if (earlyExit == false) {
         EG_RunStateMachine(pEgState);
         pEgState->triggerEntry--;
     }
     return returnValue;
 }

As stated above the actual state machine is processed by EG_RunStateMachine().

EG_RunStateMachine() must process all states, except for the dummy state (EG_FSM_STATE_DUMMY). A condensed version of the state machine runner function looks like this:

Listing 8.19 Condensed state machine runner function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 static STD_RETURN_TYPE_e EG_RunStateMachine(EG_STATE_s *pEgState) {
     STD_RETURN_TYPE_e ranStateMachine = STD_OK;
     EG_FSM_STATES_e nextState         = EG_FSM_STATE_DUMMY;
     switch (pEgState->currentState) {
         /********************************************** STATE: HAS NEVER RUN */
         case EG_FSM_STATE_HAS_NEVER_RUN:
             /* code goes here */
             break;

         /********************************************** STATE: UNINITIALIZED */
         case EG_FSM_STATE_UNINITIALIZED:
             /* code goes here */
             break;

         /********************************************* STATE: INITIALIZATION */
         case EG_FSM_STATE_INITIALIZATION:
             /* code goes here */
             break;

         /**************************************************** STATE: RUNNING */
         case EG_FSM_STATE_RUNNING:
             /* code goes here */
             break;
         /****************************************************** STATE: ERROR */
         case EG_FSM_STATE_ERROR:
             /* code goes here */
             break;

         /**************************************************** STATE: DEFAULT */
         default:
             /* all cases must be processed, trap if unknown state arrives */
             FAS_ASSERT(FAS_TRAP);
             break;
     }

     return ranStateMachine;
 }

It can now be seen why the EG_FSM_STATE_DUMMY state must never be processed by the state machine: If a function irregularly sets the state to EG_FSM_STATE_DUMMY, the state machine will switch to the default case and the FAS_ASSERT() function will stop this undefined behavior.

8.2.3.2.2. Description of the Implementation of All Cases

At next the implementations of all cases are explained in detail.

8.2.3.2.2.1. EG_FSM_STATE_HAS_NEVER_RUN

If the state machine has never run, it needs to be transferred to known state, the uninitialized state (EG_FSM_STATE_UNINITIALIZED).

Note

This section uses the function EG_SetState(). The detailed explanation of EG_SetState() is found later in the text in Section 8.2.3.3.2.

Listing 8.20 Transition from the first startup of the state machine
1
2
3
4
5
6
7
8
 switch (pEgState->currentState) {
     /********************************************** STATE: HAS NEVER RUN */
     case EG_FSM_STATE_HAS_NEVER_RUN:
         /* Nothing to do, just transfer */
         EG_SetState(pEgState, EG_FSM_STATE_UNINITIALIZED, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
         break;
     /* ... */
 }

8.2.3.2.2.2. EG_FSM_STATE_UNINITIALIZED

This is the first state that is present in the state machine example. In the example there is nothing to do in the state Uninitialized. For most applications this will also be the case. However, if needed an application can implement some behavior in this state before transferring to the state Initialization (EG_FSM_STATE_INITIALIZATION):

Listing 8.21 Transition from the first startup of the state machine
1
2
3
4
5
6
7
8
9
 switch (pEgState->currentState) {
     /* ... */
     /********************************************** STATE: UNINITIALIZED */
     case EG_FSM_STATE_UNINITIALIZED:
         /* Nothing to do, just transfer */
         EG_SetState(pEgState, EG_FSM_STATE_INITIALIZATION, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
         break;
     /* ... */
 }

8.2.3.2.2.3. EG_FSM_STATE_INITIALIZATION

The example showed, that the state Initialization consists of three substates. Putting all code for all substates directly into the state Initialization would cause bad readability and bad maintainability. Therefore all details of what happens in the state are implemented in state processing functions. Description of the Implementation of State Processing Functions explains what state processing functions are and how they work. For now it is sufficient to know that state processing functions need to exist.

If an error occurs in any of the substates of the state Initialization the state machine needs to transfer to the state Error. The transitions based on the states and substates would not be clearly visible in such a implementation. Therefore this logic is transferred into a state processing function EG_ProcessInitializationState(). State processing functions MUST use the naming pattern {MODULE_PREFIX}_Process{StateName}State where {StateName} is the state to be processed, e.g., for the state Initialization {StateName} needs to be replaced by Initialization.

The state processing function (in this example EG_ProcessInitializationState()) returns the state the state machine has to transition to.

Generally three cases can happen:

  • the state machine stays in the current state,

  • the state machine transitions to another state or

  • something went wrong and the state machine must process the error.

To reflect this, an if-else structure is used. The first if always processes the current case, i.e. staying in the current state. The final else always processes the case if something unforeseen went wrong and performs an assertion. Between the if and else all else if implement the state transitions to other states. For this example this translates into the following code:

Listing 8.22 The initialization state is processed by an own function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 switch (pEgState->currentState) {
     /* ... */
     /********************************************* STATE: INITIALIZATION */
     case EG_FSM_STATE_INITIALIZATION:
         nextState = EG_ProcessInitializationState(pEgState);
         if (nextState == EG_FSM_STATE_INITIALIZATION) {
             /* staying in state, processed by substate function */
         } else if (nextState == EG_FSM_STATE_ERROR) {
             EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
         } else if (nextState == EG_FSM_STATE_RUNNING) {
             EG_SetState(pEgState, EG_FSM_STATE_RUNNING, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
         } else {
             FAS_ASSERT(FAS_TRAP); /* Something went wrong */
         }
         break;
     /* ... */
 }

8.2.3.2.2.4. EG_FSM_STATE_RUNNING

After a successful initialization the state machine transfers into the operational mode. As described above, the state machine stays in that state until an error occurs. This state is also processed by the state function EG_ProcessRunningState() as it has more than one option to transfer to (either staying in the state or going to an error state).

Listing 8.23 The running state is also processed by a own function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 switch (pEgState->currentState) {
     /* ... */
     /**************************************************** STATE: RUNNING */
     case EG_FSM_STATE_RUNNING:
         nextState = EG_ProcessRunningState(pEgState);
         if (nextState == EG_FSM_STATE_RUNNING) {
             /* staying in state, processed by state function */
         } else if (nextState == EG_FSM_STATE_ERROR) {
             EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
         } else {
             FAS_ASSERT(FAS_TRAP); /* Something went wrong */
         }
         break;
     /* ... */
 }

8.2.3.2.2.5. EG_FSM_STATE_ERROR

This state processes the error case. Errors can be recoverable, but in this example, for the sake of simplicity, they are not.

Listing 8.24 The error state
1
2
3
4
5
6
7
8
 switch (pEgState->currentState) {
     /* ... */
     /****************************************************** STATE: ERROR */
     case EG_FSM_STATE_ERROR:
         /* implement error processing here or trap */
         break;
     /* ... */
 }

In many cases an error is recoverable. Such a situation is described in Extended Example With Recoverable Error

8.2.3.2.2.6. default

This case makes sure that all states are correctly processed and the dummy state (EG_FSM_STATE_DUMMY) is not used. If this is not the case then this function traps.

Listing 8.25 The default state
1
2
3
4
5
6
7
8
 switch (pEgState->currentState) {
     /* ... */
     /**************************************************** STATE: DEFAULT */
     default:
         /* all cases must be processed, trap if unknown state arrives */
         FAS_ASSERT(FAS_TRAP);
         break;
 }

8.2.3.2.2.7. EG_FSM_STATE_DUMMY

As already stated in default processing the EG_FSM_STATE_DUMMY state is not required. The following describes the purpose of this pseudo state. There are two reasons one additional state is needed.

The first reason is that EG_SetState() and EG_SetSubstate() needed some state to set the nextState and nextSubstate members of the struct to some valid value after the nextState is transferred to currentState and currentSubstate member. This must be some value that is not a real state the state machine could transfer to, but something to indicate that nextState and nextSubstate were cleared. EG_FSM_STATE_DUMMY is used for that purpose.

The second reason comes from the initialization of variables in C. All uninitialized struct variables are initialized with zero, therefore for this example also eg_state, which is the state variable of this state machine. This is guaranteed by the C99 standard. For details see ISO C99 Standard 6.7.8.21 (Language/Declarations/Initialization/21).

State variables store all states. These states are defined by an enum. This was described in Basics. The first entry in an unnumbered enum has the value zero. Not fully explicitly initializing the state variable would implicitly initialize it with zero.

Listing 8.26 No initialization of the state variable eg_state
1
2
 EG_STATE_s eg_state;
 /* equals to: EG_STATE_s eg_state = {0}; */

In order to prevent not thinking about the initialization of the state members, the first state is the second enum entry (in this example EG_FSM_STATE_HAS_NEVER_RUN). This equals integer value 1, not 0. This forces the developer to think about initialization and think how the state variable (here eg_state) needs to be initialized. In combination with the implementation pattern of the EG_RunStateMachine() the state machine only starts if the initialization is correctly done.

8.2.3.2.3. Description of the Implementation of State Processing Functions

Functions that process a specific state are referred to as state processing functions.

State processing functions MUST use the naming pattern {MODULE_PREFIX}_Process{StateName}State where StateName is the state to be processed, e.g., for the state Initialization StateName needs to be replaced by Initialization.

State processing functions always return the next state to transition to. A variable called nextState MUST be defined locally in such functions. This variable MUST always be initialized with the state this state processing function implements.

Generally the nextState variables definition follows the following pattern EG_FSM_STATES_e nextState = EG_FSM_STATE_{SOME_STATE} where {SOME_STATE} needs to be replaced with the state this function is processing. For example, as the function EG_ProcessInitializationState() process the state Initialization the correct state to initialize nextState with is EG_FSM_STATE_INITIALIZATION. The example in Listing 8.27 shows this more detailed for EG_ProcessInitializationState():

Listing 8.27 Example of nextState initialization
1
2
3
4
5
 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
     EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
     /* code */
     return nextState;
     }

At next the state processing functions EG_ProcessInitializationState() and EG_ProcessRunningState() are explained.

8.2.3.2.3.1. EG_ProcessInitializationState()

Note

This section uses the function EG_SetSubstate(). The detailed explanation of EG_SetSubstate() is found later in the text in Section 8.2.3.3.3.

The initialization state has three substates (I0, I1, Iexit) that are run sequentially. The Entry substate (from the enums boilerplate) just transfers the state machine in the first initialization substate I0. There is no error handling required and code reads as simple as follows:

Listing 8.28 Transferring from the Entry state in first initialization substate I0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
     EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
     switch (pEgState->currentSubstate) {
         case EG_FSM_SUBSTATE_ENTRY:
             /* Nothing to do, just transfer to next substate */
             EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_0, EG_FSM_SHORT_TIME);
             break;
         /* ... */
     }
 }

In the first substate I0 some work needs to be done (hypothetically for this example). This work is implemented in a function EG_SomeInitializationFunction0() that returns either true (if successful) or false (if unsuccessful). If it was unsuccessful, the substate I0 failed and the state machine needs to transfer into the state Error. If this substate was successful the Initialization state should precede with the second substate I1. The code below shows the implementation.

Listing 8.29 Transferring from the first initialization substate I0 to second initialization substate I1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
     EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
     switch (pEgState->currentSubstate) {
         /* ... */
         case EG_FSM_SUBSTATE_INITIALIZATION_0:
             if (true == EG_SomeInitializationFunction0()) {
                 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_1, EG_FSM_SHORT_TIME);
             } else {
                 /* Something might go wrong, so transition to error state */
                 nextState = EG_FSM_STATE_ERROR;
             }
             break;
         /* ... */
     }
 }

Transferring from initialization substate I1 to initialization substate Iexit works similar, therefore this implementation is left out. At next the transition from the initialization substate Iexit into the next state, the first running substate R0, is shown. The function EG_SomeInitializationFunctionExit() behaves the same way EG_SomeInitializationFunction0() above does. This leads to the following implementation:

Listing 8.30 Transferring from the last initialization substate Iexit to first running substate R0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
     EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
     switch (pEgState->currentSubstate) {
         /* ... */
         case EG_FSM_SUBSTATE_INITIALIZATION_EXIT:
             if (true == EG_SomeInitializationFunctionExit()) {
                 /* Initialization was successful, so transition to running state */
                 nextState = EG_FSM_STATE_RUNNING;
             } else {
                 /* Something might go wrong, so transition to error state */
                 nextState = EG_FSM_STATE_ERROR;
             }
             break;
         /* ... */

     }
 }

The default case is implemented to assert on illegal substates:

Listing 8.31 Assertion on illegal substate
1
2
3
4
5
6
7
8
9
 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
     EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
     switch (pEgState->currentSubstate) {
         /* ... */
     default:
         FAS_ASSERT(FAS_TRAP);
         break;
     }
 }

8.2.3.2.3.2. EG_ProcessRunningState()

The state Running consists of three substates that are looped in order ( R0, R1, R2, R0, R1, R2, R0, …) as long as no error occurs. If an error occurs in any Running state’s substates the next state is the state Error.

In all of the Running state’s substates some work needs to be done (again, hypothetically for this example). This work is implemented in the functions EG_SomeRunningFunction0() for substate R0, EG_SomeRunningFunction1() for substate R1 and EG_SomeRunningFunction2() for substate R2 that return either true (if successful) or false (if unsuccessful). If it was unsuccessful, the respective next state is the state Error. If it was successful, the respective next substate will be run. The implementation is shown below:

Listing 8.32 The Running and its substates implemented in its state processing function
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 static EG_FSM_STATES_e EG_ProcessRunningState(EG_STATE_s *pEgState) {
     EG_FSM_STATES_e nextState = EG_FSM_STATE_RUNNING; /* default behavior: stay in state */
     switch (pEgState->currentSubstate) {
         case EG_FSM_SUBSTATE_ENTRY:
             /* Nothing to do, just transfer to next substate */
             EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
             break;

         case EG_FSM_SUBSTATE_RUNNING_0:
             if (true == EG_SomeRunningFunction0()) {
                 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_1, EG_FSM_SHORT_TIME);
             } else {
                 /* Something might go wrong, so transition to error state */
                 nextState = EG_FSM_STATE_ERROR;
             }
             break;

         case EG_FSM_SUBSTATE_RUNNING_1:
             if (true == EG_SomeRunningFunction1()) {
                 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_2, EG_FSM_SHORT_TIME);
             } else {
                 /* Something might go wrong, so transition to error state */
                 nextState = EG_FSM_STATE_ERROR;
             }
             break;

         case EG_FSM_SUBSTATE_RUNNING_2:
             if (true == EG_SomeRunningFunction2()) {
                 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
             } else {
                 /* Something might go wrong, so transition to error state */
                 nextState = EG_FSM_STATE_ERROR;
             }
             break;

         default:
             FAS_ASSERT(FAS_TRAP);
             break;
     }
     return nextState;
 }

8.2.3.3. Generic Functions Used in the State Machine

The following functions (EG_CheckMultipleCalls, EG_SetState, EG_SetSubstate) are needed for all state machines.

8.2.3.3.1. EG_CheckMultipleCalls()

The state machine trigger function (here EG_Trigger) MUST only be called time or event triggered and MUST NOT be called multiple times (no reentrance).

EG_CheckMultipleCalls() checks based on triggerEntry if the function is called only one time. The triggerEntry variable must be incremented once in each call of this function. It must be decremented once in every call of the trigger function, no matter what the trigger function does (this means even if the timer has not elapsed).

8.2.3.3.2. EG_SetState()

This function sets the next state. The following steps are performed:

  • setting the idle time of a state and

  • setting the state and substate.

Function behavior:

If neither, the state or substate have changed, there is no action to be taken. If the state has changed, the state and the substate need to change. The state is set to the next state and the substate is set to the entry state for substates (EG_FSM_SUBSTATE_ENTRY). After that the nextState and nextSubstate of state and substate can be cleared (set to EG_FSM_STATE_DUMMY and EG_FSM_SUBSTATE_DUMMY respectively). If the state has not changed, and only the substate has, the next substate is set by EG_SetSubstate().

This implementation requires that every state has a defined entry for all states and all states need to implement that entry. This also ensure no state transitions from e.g.

  • State A and third substate into

  • State C and second substate

are made, but a strict chain needs to be followed:

  • State A and third substate into

  • State C and first substate (EG_FSM_SUBSTATE_ENTRY) into

  • State C and second substate.

What if there is no substate in a case?: There might be states that do not need substate, even this example has three states with no substates ( EG_FSM_STATE_HAS_NEVER_RUN, EG_FSM_STATE_UNINITIALIZED and EG_FSM_STATE_ERROR). In this case just the transition(s) in the next state(s) need to be implemented and no state processing function needs to be implemented. Therefore setting the substate implicitly by using the EG_SetState is fine, as the substate is ignored in that case and it is correctly set to entry (EG_FSM_SUBSTATE_ENTRY) for the next case, wether this state implements substates or not.

8.2.3.3.3. EG_SetSubstate()

This function only sets the substate.

When currentSubstate is set to the next substate, the nextSubstate can be cleared. This is done by setting it to the dummy substate (EG_FSM_SUBSTATE_DUMMY).

8.2.3.4. Extended Example With Recoverable Error

There are cases where an error during the processing of the state machine can occur and there are strategies to recover from them. The example from Fig. 8.1 is extended as follows:

# Copyright (c) 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# We kindly request you to use one or more of the following phrases to refer to
# foxBMS in your hardware, software, documentation or advertising materials:
#
# - "This product uses parts of foxBMS®"
# - "This product includes parts of foxBMS®"
# - "This product is derived from foxBMS®"

digraph fsm_recoverable_error {
    rankdir=LR;
    size="8,5"
    node [shape = doublecircle] nd_uninitialized nd_error;
    node [shape = circle] nd_initialization nd_running;

    nd_uninitialized        [label=<<B>Uninitialized</B>>];
    nd_error                [label=<<B>Error</B>>];
    nd_initialization       [label=<<B>Initialization</B>>];
    nd_running              [label=<<B>Running</B>>];

    nd_uninitialized ->nd_initialization    [label = "Initialize"];
    nd_initialization -> nd_running         [label = ""];
    nd_running -> nd_error                  [label = "Runtime error"];
    nd_running -> nd_running                [label = "Operational mode"];
    nd_error -> nd_uninitialized            [ style=dashed, label = "Recover" ];
    nd_initialization -> nd_error           [label = "Initialization error"];
}

Fig. 8.4 Example with recoverable error

To implement this behavior, the error case needs to be changed to something like shown in Listing 8.33. There is a state function EG_ProcessErrorState() to process the error case and there might be an option to re-initialize the state machine based on the type of error.

Listing 8.33 The error state
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 switch (pEgState->currentState) {
     /* ... */
     /****************************************************** STATE: ERROR */
     case EG_FSM_STATE_ERROR:
         nextState = EG_ProcessErrorState(pEgState);
         if (nextState == EG_FSM_STATE_ERROR) {
             /* staying in error state, processed by state function */
         } else if (nextState == EG_FSM_STATE_UNINITIALIZED) {
             EG_SetState(pEgState, EG_FSM_STATE_UNINITIALIZED, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
         } else {
             FAS_ASSERT(FAS_TRAP); /* Something went wrong */
         }
         break;
     /* ... */
 }

8.2.3.5. Full Example Code

The full implementation of this state machine is found in Listing 8.34 and Listing 8.35.

Listing 8.34 The header of the state machine
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
 *
 * @copyright &copy; 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * We kindly request you to use one or more of the following phrases to refer to
 * foxBMS in your hardware, software, documentation or advertising materials:
 *
 * - &Prime;This product uses parts of foxBMS&reg;&Prime;
 * - &Prime;This product includes parts of foxBMS&reg;&Prime;
 * - &Prime;This product is derived from foxBMS&reg;&Prime;
 *
 */

/**
 * @file    state-machine.h
 * @author  foxBMS Team
 * @date    2020-10-29 (date of creation)
 * @updated 2020-11-09 (date of last update)
 * @ingroup STATE_MACHINE
 * @prefix  EG
 *
 * @brief   Header file of some software
 *
 */

#ifndef FOXBMS__STATE_MACHINE_H_
#define FOXBMS__STATE_MACHINE_H_

/*========== Includes =======================================================*/
#include "general.h"

/*========== Macros and Definitions =========================================*/
/** States of the state machine */
typedef enum EG_FSM_STATES {
    EG_FSM_STATE_DUMMY,          /*!< dummy state - always the first state */
    EG_FSM_STATE_HAS_NEVER_RUN,  /*!< never run state - always the second state */
    EG_FSM_STATE_UNINITIALIZED,  /*!< uninitialized state */
    EG_FSM_STATE_INITIALIZATION, /*!< initializing the state machine */
    EG_FSM_STATE_RUNNING,        /*!< operational mode of the state machine  */
    EG_FSM_STATE_ERROR,          /*!< state for error processing  */
} EG_FSM_STATES_e;

/** Substates of the state machine */
typedef enum EG_FSM_SUBSTATES {
    EG_FSM_SUBSTATE_DUMMY,               /*!< dummy state - always the first substate */
    EG_FSM_SUBSTATE_ENTRY,               /*!< entry state - always the second substate */
    EG_FSM_SUBSTATE_INITIALIZATION_0,    /*!< fist initialization substate */
    EG_FSM_SUBSTATE_INITIALIZATION_1,    /*!< second initialization substate */
    EG_FSM_SUBSTATE_INITIALIZATION_EXIT, /*!< last initialization substate */
    EG_FSM_SUBSTATE_RUNNING_0,           /*!< fist running substate */
    EG_FSM_SUBSTATE_RUNNING_1,           /*!< second running substate */
    EG_FSM_SUBSTATE_RUNNING_2,           /*!< third running substate */
} EG_FSM_SUBSTATES_e;

/** some struct with some information */
typedef struct EG_INFORMATION {
    uint8_t r0; /*!< some info 0 */
    uint8_t r1; /*!< some info 0 */
    uint8_t r2; /*!< some info 0 */
} EG_INFORMATION_s;

/** This struct describes the state of the monitoring instance */
typedef struct EG_STATE {
    uint16_t timer;                      /*!< timer of the state */
    uint8_t triggerEntry;                /*!< trigger entry of the state */
    EG_FSM_STATES_e nextState;           /*!< next state of the FSM */
    EG_FSM_STATES_e currentState;        /*!< current state of the FSM */
    EG_FSM_STATES_e previousState;       /*!< previous state of the FSM */
    EG_FSM_SUBSTATES_e nextSubstate;     /*!< next substate of the FSM */
    EG_FSM_SUBSTATES_e currentSubstate;  /*!< current substate of the FSM */
    EG_FSM_SUBSTATES_e previousSubstate; /*!< previous substate of the FSM */
    EG_INFORMATION_s information;        /*!< Some information to be stored */
} EG_STATE_s;

/*========== Extern Constant and Variable Declarations ======================*/

/** state of the example state machine */
extern EG_STATE_s eg_state;

/*========== Extern Function Prototypes =====================================*/
/**
 * @brief   tick function, call this to advance the state machine
 * @param   pEgState current state of the state machine
 * @returns returns always #STD_OK
 */
extern STD_RETURN_TYPE_e EG_Trigger(EG_STATE_s *pEgState);

/*========== Externalized Static Functions Prototypes (Unit Test) ===========*/

#endif /* FOXBMS__STATE_MACHINE_H_ */
Listing 8.35 The implementation of the state machine
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/**
 *
 * @copyright &copy; 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * We kindly request you to use one or more of the following phrases to refer to
 * foxBMS in your hardware, software, documentation or advertising materials:
 *
 * - &Prime;This product uses parts of foxBMS&reg;&Prime;
 * - &Prime;This product includes parts of foxBMS&reg;&Prime;
 * - &Prime;This product is derived from foxBMS&reg;&Prime;
 *
 */

/**
 * @file    state-machine.c
 * @author  foxBMS Team
 * @date    2020-10-29 (date of creation)
 * @updated 2020-11-09 (date of last update)
 * @ingroup STATE_MACHINE
 * @prefix  EG
 *
 * @brief   Implementation of some driver that needs a state machine
 *
 */

/*========== Includes =======================================================*/
#include "state-machine.h"

/*========== Macros and Definitions =========================================*/
/**
 * statemachine short time definition in #EG_Trigger calls until next state is
 * processed
 */
#define EG_FSM_SHORT_TIME (1u)

/**
 * statemachine medium time definition in #EG_Trigger calls until next
 * state/substate is processed
 */
#define EG_FSM_MEDIUM_TIME (5u)

/**
 * statemachine long time definition in #EG_Trigger calls until next
 * state/substate is processed
 */
#define EG_FSM_LONG_TIME (10u)

/** Symbolic names to check for multiple calls of #EG_Trigger */
typedef enum EG_CHECK_MULTIPLE_CALLS {
    EG_MULTIPLE_CALLS_NO,  /*!< no multiple calls, OK */
    EG_MULTIPLE_CALLS_YES, /*!< multiple calls, not OK */
} EG_CHECK_MULTIPLE_CALLS_e;

/*========== Static Constant and Variable Definitions =======================*/

/*========== Extern Constant and Variable Definitions =======================*/

/** local instance of the driver-state */
EG_STATE_s eg_state = {
    .timer            = 0,
    .triggerEntry     = 0,
    .nextState        = EG_FSM_STATE_HAS_NEVER_RUN,
    .currentState     = EG_FSM_STATE_HAS_NEVER_RUN,
    .previousState    = EG_FSM_STATE_HAS_NEVER_RUN,
    .nextSubstate     = EG_FSM_SUBSTATE_DUMMY,
    .currentSubstate  = EG_FSM_SUBSTATE_DUMMY,
    .previousSubstate = EG_FSM_SUBSTATE_DUMMY,
    .information.r0   = 0,
    .information.r1   = 0,
    .information.r2   = 0,
};

/*========== Static Function Prototypes =====================================*/
/**
 * @brief   check for multiple calls of state machine trigger function
 * @details The trigger function is not reentrant, which means it cannot
 *          be called multiple times. This functions increments the
 *          triggerEntry counter once and must be called each time the
 *          trigger function is called. If triggerEntry is greater than
 *          one, there were multiple calls. For this function to work,
 *          triggerEntry must be decremented each time the trigger function
 *          is called, even if no processing do because the timer is
 *          non-zero.
 * @param   pEgState state of the fake state machine
 * @return  #EG_MULTIPLE_CALLS_YES if there were multiple calls,
 *          #EG_MULTIPLE_CALLS_NO otherwise
 */
static EG_CHECK_MULTIPLE_CALLS_e EG_CheckMultipleCalls(EG_STATE_s *pEgState);

/**
 * @brief   Sets the next state, the next substate and the timer value
 *          of the state variable.
 * @param   pEgState       state of the example state machine
 * @param   nextState      state to be transferred into
 * @param   nextSubstate   substate to be transferred into
 * @param   idleTime       wait time for the state machine
 */
static void EG_SetState(
    EG_STATE_s *pEgState,
    EG_FSM_STATES_e nextState,
    EG_FSM_SUBSTATES_e nextSubstate,
    uint16_t idleTime);

/**
 * @brief   Sets the next substate and the timer value
 *          of the state variable.
 * @param   pEgState       state of the example state machine
 * @param   nextSubstate   substate to be transferred into
 * @param   idleTime       wait time for the state machine
 */
static void EG_SetSubstate(EG_STATE_s *pEgState, EG_FSM_SUBSTATES_e nextSubstate, uint16_t idleTime);

/**
 * @brief   dummy function for initialization substate
 *          #EG_FSM_SUBSTATE_INITIALIZATION_0
 * @return  returns always true
 */
static bool EG_SomeInitializationFunction0(void);

/**
 * @brief   dummy function for initialization substate
 *          #EG_FSM_SUBSTATE_INITIALIZATION_1
 * @return  returns always true
 */
static bool EG_SomeInitializationFunction1(void);

/**
 * @brief   dummy function to check if the initialization
 *          step of the state machine was successfull
 *          (#EG_FSM_SUBSTATE_INITIALIZATION_1)
 * @return  returns always true
 */
static bool EG_SomeInitializationFunctionExit(void);

/**
 * @brief   dummy function making a test to determine
 *          the outcome of substate #EG_FSM_SUBSTATE_RUNNING_0
 * @return  returns always true
 */
static bool EG_SomeRunningFunction0(void);

/**
 * @brief   dummy function making a test to determine
 *          the outcome of substate EG_FSM_SUBSTATE_RUNNING_1
 * @return  returns always true
 */
static bool EG_SomeRunningFunction1(void);

/**
 * @brief   dummy function making a test to determine
 *          the outcome of substate EG_FSM_SUBSTATE_RUNNING_2
 * @return  returns always true
 */
static bool EG_SomeRunningFunction2(void);

/**
 * @brief   Processes the initialization state
 * @param   pEgState state of the example state machine
 * @return  Always #STD_OK
 */
static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState);

/**
 * @brief   Processes the running state
 * @param   pEgState state of the example state machine
 * @return  Always #STD_OK
 */
static EG_FSM_STATES_e EG_ProcessRunningState(EG_STATE_s *pEgState);

/**
 * @brief   Defines the state transitions
 * @details This function contains the implementation of the state
 *          machine, i.e., the sequence of states and substates.
 *          It is called by the trigger function every time
 *          the state machine timer has a non-zero value.
 * @param   pEgState state of the example state machine
 * @return  Always #STD_OK
 */
static STD_RETURN_TYPE_e EG_RunStateMachine(EG_STATE_s *pEgState);

/*========== Static Function Implementations ================================*/

static EG_CHECK_MULTIPLE_CALLS_e EG_CheckMultipleCalls(EG_STATE_s *pEgState) {
    FAS_ASSERT(pEgState != NULL_PTR);
    EG_CHECK_MULTIPLE_CALLS_e multipleCalls = EG_MULTIPLE_CALLS_NO;
    OS_EnterTaskCritical();
    if (pEgState->triggerEntry == 0u) {
        pEgState->triggerEntry++;
    } else {
        multipleCalls = EG_MULTIPLE_CALLS_YES; /* multiple call of function EG_Trigger for instance pEgState */
    }
    OS_ExitTaskCritical();
    return multipleCalls;
}

static void EG_SetState(
    EG_STATE_s *pEgState,
    EG_FSM_STATES_e nextState,
    EG_FSM_SUBSTATES_e nextSubstate,
    uint16_t idleTime) {
    FAS_ASSERT(pEgState != NULL_PTR);
    bool earlyExit = false;

    pEgState->timer = idleTime;

    if ((pEgState->currentState == nextState) && (pEgState->currentSubstate == nextSubstate)) {
        /* Next state and next substate equal to current state and substate: nothing to do */
        pEgState->nextState    = EG_FSM_STATE_DUMMY;    /* no state transistion required -> reset */
        pEgState->nextSubstate = EG_FSM_SUBSTATE_DUMMY; /* no substate transistion required -> reset */
        earlyExit              = true;
    }

    if (earlyExit == false) {
        if (pEgState->currentState != nextState) {
            /* Next state is different: switch to it and set substate to entry value */
            pEgState->previousState    = pEgState->currentState;
            pEgState->currentState     = nextState;
            pEgState->previousSubstate = pEgState->currentSubstate;
            pEgState->currentSubstate  = EG_FSM_SUBSTATE_ENTRY; /* Use entry state after a top level state change */
            pEgState->nextState        = EG_FSM_STATE_DUMMY;    /* no state transistion required -> reset */
            pEgState->nextSubstate     = EG_FSM_SUBSTATE_DUMMY; /* no substate transistion required -> reset */
        } else if (pEgState->currentSubstate != nextSubstate) {
            /* Only the next substate is different, switch to it */
            EG_SetSubstate(pEgState, nextSubstate, idleTime);
        } else {
            ;
        }
    }
}

static void EG_SetSubstate(EG_STATE_s *pEgState, EG_FSM_SUBSTATES_e nextSubstate, uint16_t idleTime) {
    FAS_ASSERT(pEgState != NULL_PTR);
    pEgState->timer            = idleTime;
    pEgState->previousSubstate = pEgState->currentSubstate;
    pEgState->currentSubstate  = nextSubstate;
    pEgState->nextSubstate     = EG_FSM_SUBSTATE_DUMMY; /* substate has been set, now reset value for nextSubstate */
}

static bool EG_SomeInitializationFunction0(void) {
    return true;
}

static bool EG_SomeInitializationFunction1(void) {
    return true;
}

static bool EG_SomeInitializationFunctionExit(void) {
    return true;
}

static bool EG_SomeRunningFunction0(void) {
    return true;
}

static bool EG_SomeRunningFunction1(void) {
    return true;
}

static bool EG_SomeRunningFunction2(void) {
    return true;
}

static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
    EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
    switch (pEgState->currentSubstate) {
        case EG_FSM_SUBSTATE_ENTRY:
            /* Nothing to do, just transfer to next substate */
            EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_0, EG_FSM_SHORT_TIME);
            break;

        case EG_FSM_SUBSTATE_INITIALIZATION_0:
            if (true == EG_SomeInitializationFunction0()) {
                EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_1, EG_FSM_SHORT_TIME);
            } else {
                /* Something went wrong, so transition to error state */
                nextState = EG_FSM_STATE_ERROR;
            }
            break;

        case EG_FSM_SUBSTATE_INITIALIZATION_1:
            if (true == EG_SomeInitializationFunction1()) {
                EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_EXIT, EG_FSM_SHORT_TIME);
            } else {
                /* Something went wrong, so transition to error state */
                nextState = EG_FSM_STATE_ERROR;
            }
            break;

        case EG_FSM_SUBSTATE_INITIALIZATION_EXIT:
            if (true == EG_SomeInitializationFunctionExit()) {
                /* Initialization was successful, so transition to running state */
                nextState = EG_FSM_STATE_RUNNING;
            } else {
                /* Something went wrong, so transition to error state */
                nextState = EG_FSM_STATE_ERROR;
            }
            break;

        default:
            FAS_ASSERT(FAS_TRAP);
            break;
    }
    return nextState;
}

static EG_FSM_STATES_e EG_ProcessRunningState(EG_STATE_s *pEgState) {
    EG_FSM_STATES_e nextState = EG_FSM_STATE_RUNNING; /* default behavior: stay in state */
    switch (pEgState->currentSubstate) {
        case EG_FSM_SUBSTATE_ENTRY:
            /* Nothing to do, just transfer to next substate */
            EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
            break;

        case EG_FSM_SUBSTATE_RUNNING_0:
            if (true == EG_SomeRunningFunction0()) {
                EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_1, EG_FSM_SHORT_TIME);
            } else {
                /* Something went wrong, so transition to error state */
                nextState = EG_FSM_STATE_ERROR;
            }
            break;

        case EG_FSM_SUBSTATE_RUNNING_1:
            if (true == EG_SomeRunningFunction1()) {
                EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_2, EG_FSM_SHORT_TIME);
            } else {
                /* Something went wrong, so transition to error state */
                nextState = EG_FSM_STATE_ERROR;
            }
            break;

        case EG_FSM_SUBSTATE_RUNNING_2:
            if (true == EG_SomeRunningFunction2()) {
                EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
            } else {
                /* Something went wrong, so transition to error state */
                nextState = EG_FSM_STATE_ERROR;
            }
            break;

        default:
            FAS_ASSERT(FAS_TRAP);
            break;
    }

    return nextState;
}

static STD_RETURN_TYPE_e EG_RunStateMachine(EG_STATE_s *pEgState) {
    STD_RETURN_TYPE_e ranStateMachine = STD_OK;
    EG_FSM_STATES_e nextState         = EG_FSM_STATE_DUMMY;
    switch (pEgState->currentState) {
        /********************************************** STATE: HAS NEVER RUN */
        case EG_FSM_STATE_HAS_NEVER_RUN:
            /* Nothing to do, just transfer */
            EG_SetState(pEgState, EG_FSM_STATE_UNINITIALIZED, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
            break;

        /********************************************** STATE: UNINITIALIZED */
        case EG_FSM_STATE_UNINITIALIZED:
            /* Nothing to do, just transfer */
            EG_SetState(pEgState, EG_FSM_STATE_INITIALIZATION, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
            break;

        /********************************************* STATE: INITIALIZATION */
        case EG_FSM_STATE_INITIALIZATION:
            nextState = EG_ProcessInitializationState(pEgState);
            if (nextState == EG_FSM_STATE_INITIALIZATION) {
                /* staying in state, processed by state function */
            } else if (nextState == EG_FSM_STATE_ERROR) {
                EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
            } else if (nextState == EG_FSM_STATE_RUNNING) {
                EG_SetState(pEgState, EG_FSM_STATE_RUNNING, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
            } else {
                FAS_ASSERT(FAS_TRAP); /* Something went wrong */
            }
            break;

        /**************************************************** STATE: RUNNING */
        case EG_FSM_STATE_RUNNING:
            nextState = EG_ProcessRunningState(pEgState);
            if (nextState == EG_FSM_STATE_RUNNING) {
                /* staying in state, processed by state function */
            } else if (nextState == EG_FSM_STATE_ERROR) {
                EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
            } else {
                FAS_ASSERT(FAS_TRAP); /* Something went wrong */
            }
            break;

        /****************************************************** STATE: ERROR */
        case EG_FSM_STATE_ERROR:
            /* implement error processing here or trap */
            break;

        /**************************************************** STATE: DEFAULT */
        default:
            /* all cases must be processed, trap if unknown state arrives */
            FAS_ASSERT(FAS_TRAP);
            break;
    }

    return ranStateMachine;
}

/*========== Extern Function Implementations ================================*/
extern STD_RETURN_TYPE_e EG_Trigger(EG_STATE_s *pEgState) {
    FAS_ASSERT(pEgState != NULL_PTR);
    bool earlyExit                = false;
    STD_RETURN_TYPE_e returnValue = STD_OK;

    /* Check multiple calls of function */
    if (EG_MULTIPLE_CALLS_YES == EG_CheckMultipleCalls(pEgState)) {
        returnValue = STD_NOT_OK;
        earlyExit   = true;
    }

    if (earlyExit == false) {
        if (pEgState->timer > 0u) {
            if ((--pEgState->timer) > 0u) {
                pEgState->triggerEntry--;
                returnValue = STD_OK;
                earlyExit   = true;
            }
        }
    }

    if (earlyExit == false) {
        EG_RunStateMachine(pEgState);
        pEgState->triggerEntry--;
    }
    return returnValue;
}

/*========== Externalized Static Function Implementations (Unit Test) =======*/