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 AFE 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"];
}](../../_images/graphviz-d16289205e250d131f0ffe0ae935a79f82f59f0c.png)
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
}](../../_images/graphviz-d42469c2ff1b4be1e765da2f4df5bdfec83b134e.png)
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
}](../../_images/graphviz-7c10992fc5d16c3b3cc0026df86876a7900736c1.png)
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 bynoArguments()
.The function
uint8_t addTwoNumbers(uint8_t a, uint8_t b)
is referred to byaddTwoNumbers()
.
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:
1 typedef enum EG_FSM_STATES {
2 EG_FSM_STATE_DUMMY, /*!< dummy state - always the first state */
3 EG_FSM_STATE_HAS_NEVER_RUN, /*!< never run state - always the second state */
4 EG_FSM_STATE_UNINITIALIZED, /*!< uninitialized state */
5 EG_FSM_STATE_INITIALIZATION, /*!< initializing the state machine */
6 EG_FSM_STATE_RUNNING, /*!< operational mode of the state machine */
7 EG_FSM_STATE_ERROR, /*!< state for error processing */
8 } 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:
1 typedef enum EG_FSM_SUBSTATES {
2 EG_FSM_SUBSTATE_DUMMY, /*!< dummy state - always the first substate */
3 EG_FSM_SUBSTATE_ENTRY, /*!< entry state - always the second substate */
4 EG_FSM_SUBSTATE_INITIALIZATION_0, /*!< fist initialization substate */
5 EG_FSM_SUBSTATE_INITIALIZATION_1, /*!< second initialization substate */
6 EG_FSM_SUBSTATE_INITIALIZATION_EXIT, /*!< last initialization substate */
7 EG_FSM_SUBSTATE_RUNNING_0, /*!< fist running substate */
8 EG_FSM_SUBSTATE_RUNNING_1, /*!< second running substate */
9 EG_FSM_SUBSTATE_RUNNING_2, /*!< third running substate */
10 } 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.
1 typedef struct EG_STATE {
2 uint16_t timer; /*!< timer of the state */
3 uint8_t triggerEntry; /*!< trigger entry of the state */
4 EG_FSM_STATES_e nextState; /*!< next state of the FSM */
5 EG_FSM_STATES_e currentState; /*!< current state of the FSM */
6 EG_FSM_STATES_e previousState; /*!< previous state of the FSM */
7 EG_FSM_SUBSTATES_e nextSubstate; /*!< next substate of the FSM */
8 EG_FSM_SUBSTATES_e currentSubstate; /*!< current substate of the FSM */
9 EG_FSM_SUBSTATES_e previousSubstate; /*!< previous substate of the FSM */
10 EG_INFORMATION_s information; /*!< Some information to be stored */
11 } 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
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.
1 EG_STATE_s eg_state = {
2 .timer = 0,
3 .triggerEntry = 0,
4 .nextState = EG_FSM_STATE_HAS_NEVER_RUN,
5 .currentState = EG_FSM_STATE_HAS_NEVER_RUN,
6 .previousState = EG_FSM_STATE_HAS_NEVER_RUN,
7 .nextSubstate = EG_FSM_SUBSTATE_DUMMY,
8 .currentSubstate = EG_FSM_SUBSTATE_DUMMY,
9 .previousSubstate = EG_FSM_SUBSTATE_DUMMY,
10 .information.r0 = 0,
11 .information.r1 = 0,
12 .information.r2 = 0,
13 };
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_TriggerAfe()
).
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
.
1 extern STD_RETURN_TYPE_e EG_Trigger(EG_STATE_s *pEgState) {
2 FAS_ASSERT(pEgState != NULL_PTR);
3 bool earlyExit = false;
4 STD_RETURN_TYPE_e returnValue = STD_OK;
5
6 /* Check re-entrance of function */
7 if (EG_MULTIPLE_CALLS_YES == EG_CheckMultipleCalls(pEgState)) {
8 returnValue = STD_NOT_OK;
9 earlyExit = true;
10 }
11
12 if (earlyExit == false) {
13 if (pEgState->timer > 0u) {
14 if ((--pEgState->timer) > 0u) {
15 pEgState->triggerEntry--;
16 returnValue = STD_OK;
17 earlyExit = true;
18 }
19 }
20 }
21
22 if (earlyExit == false) {
23 EG_RunStateMachine(pEgState);
24 pEgState->triggerEntry--;
25 }
26 return returnValue;
27 }
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:
1 static STD_RETURN_TYPE_e EG_RunStateMachine(EG_STATE_s *pEgState) {
2 STD_RETURN_TYPE_e ranStateMachine = STD_OK;
3 EG_FSM_STATES_e nextState = EG_FSM_STATE_DUMMY;
4 switch (pEgState->currentState) {
5 /********************************************** STATE: HAS NEVER RUN */
6 case EG_FSM_STATE_HAS_NEVER_RUN:
7 /* code goes here */
8 break;
9
10 /********************************************** STATE: UNINITIALIZED */
11 case EG_FSM_STATE_UNINITIALIZED:
12 /* code goes here */
13 break;
14
15 /********************************************* STATE: INITIALIZATION */
16 case EG_FSM_STATE_INITIALIZATION:
17 /* code goes here */
18 break;
19
20 /**************************************************** STATE: RUNNING */
21 case EG_FSM_STATE_RUNNING:
22 /* code goes here */
23 break;
24 /****************************************************** STATE: ERROR */
25 case EG_FSM_STATE_ERROR:
26 /* code goes here */
27 break;
28
29 /**************************************************** STATE: DEFAULT */
30 default:
31 /* all cases must be processed, trap if unknown state arrives */
32 FAS_ASSERT(FAS_TRAP);
33 break;
34 }
35
36 return ranStateMachine;
37 }
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.
1 switch (pEgState->currentState) {
2 /********************************************** STATE: HAS NEVER RUN */
3 case EG_FSM_STATE_HAS_NEVER_RUN:
4 /* Nothing to do, just transfer */
5 EG_SetState(pEgState, EG_FSM_STATE_UNINITIALIZED, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
6 break;
7 /* ... */
8 }
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
):
1 switch (pEgState->currentState) {
2 /* ... */
3 /********************************************** STATE: UNINITIALIZED */
4 case EG_FSM_STATE_UNINITIALIZED:
5 /* Nothing to do, just transfer */
6 EG_SetState(pEgState, EG_FSM_STATE_INITIALIZATION, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
7 break;
8 /* ... */
9 }
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:
1 switch (pEgState->currentState) {
2 /* ... */
3 /********************************************* STATE: INITIALIZATION */
4 case EG_FSM_STATE_INITIALIZATION:
5 nextState = EG_ProcessInitializationState(pEgState);
6 if (nextState == EG_FSM_STATE_INITIALIZATION) {
7 /* staying in state, processed by substate function */
8 } else if (nextState == EG_FSM_STATE_ERROR) {
9 EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
10 } else if (nextState == EG_FSM_STATE_RUNNING) {
11 EG_SetState(pEgState, EG_FSM_STATE_RUNNING, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
12 } else {
13 FAS_ASSERT(FAS_TRAP); /* Something went wrong */
14 }
15 break;
16 /* ... */
17 }
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).
1 switch (pEgState->currentState) {
2 /* ... */
3 /**************************************************** STATE: RUNNING */
4 case EG_FSM_STATE_RUNNING:
5 nextState = EG_ProcessRunningState(pEgState);
6 if (nextState == EG_FSM_STATE_RUNNING) {
7 /* staying in state, processed by state function */
8 } else if (nextState == EG_FSM_STATE_ERROR) {
9 EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
10 } else {
11 FAS_ASSERT(FAS_TRAP); /* Something went wrong */
12 }
13 break;
14 /* ... */
15 }
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.
1 switch (pEgState->currentState) {
2 /* ... */
3 /****************************************************** STATE: ERROR */
4 case EG_FSM_STATE_ERROR:
5 /* implement error processing here or trap */
6 break;
7 /* ... */
8 }
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.
1 switch (pEgState->currentState) {
2 /* ... */
3 /**************************************************** STATE: DEFAULT */
4 default:
5 /* all cases must be processed, trap if unknown state arrives */
6 FAS_ASSERT(FAS_TRAP);
7 break;
8 }
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.
1 EG_STATE_s eg_state;
2 /* 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()
:
1 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
2 EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
3 /* code */
4 return nextState;
5 }
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:
1 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
2 EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
3 switch (pEgState->currentSubstate) {
4 case EG_FSM_SUBSTATE_ENTRY:
5 /* Nothing to do, just transfer to next substate */
6 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_0, EG_FSM_SHORT_TIME);
7 break;
8 /* ... */
9 }
10 }
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.
1 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
2 EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
3 switch (pEgState->currentSubstate) {
4 /* ... */
5 case EG_FSM_SUBSTATE_INITIALIZATION_0:
6 if (true == EG_SomeInitializationFunction0()) {
7 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_1, EG_FSM_SHORT_TIME);
8 } else {
9 /* Something might go wrong, so transition to error state */
10 nextState = EG_FSM_STATE_ERROR;
11 }
12 break;
13 /* ... */
14 }
15 }
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:
1 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
2 EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
3 switch (pEgState->currentSubstate) {
4 /* ... */
5 case EG_FSM_SUBSTATE_INITIALIZATION_EXIT:
6 if (true == EG_SomeInitializationFunctionExit()) {
7 /* Initialization was successful, so transition to running state */
8 nextState = EG_FSM_STATE_RUNNING;
9 } else {
10 /* Something might go wrong, so transition to error state */
11 nextState = EG_FSM_STATE_ERROR;
12 }
13 break;
14 /* ... */
15
16 }
17 }
The default
case is implemented to assert on illegal substates:
1 static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
2 EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
3 switch (pEgState->currentSubstate) {
4 /* ... */
5 default:
6 FAS_ASSERT(FAS_TRAP);
7 break;
8 }
9 }
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:
1 static EG_FSM_STATES_e EG_ProcessRunningState(EG_STATE_s *pEgState) {
2 EG_FSM_STATES_e nextState = EG_FSM_STATE_RUNNING; /* default behavior: stay in state */
3 switch (pEgState->currentSubstate) {
4 case EG_FSM_SUBSTATE_ENTRY:
5 /* Nothing to do, just transfer to next substate */
6 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
7 break;
8
9 case EG_FSM_SUBSTATE_RUNNING_0:
10 if (true == EG_SomeRunningFunction0()) {
11 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_1, EG_FSM_SHORT_TIME);
12 } else {
13 /* Something might go wrong, so transition to error state */
14 nextState = EG_FSM_STATE_ERROR;
15 }
16 break;
17
18 case EG_FSM_SUBSTATE_RUNNING_1:
19 if (true == EG_SomeRunningFunction1()) {
20 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_2, EG_FSM_SHORT_TIME);
21 } else {
22 /* Something might go wrong, so transition to error state */
23 nextState = EG_FSM_STATE_ERROR;
24 }
25 break;
26
27 case EG_FSM_SUBSTATE_RUNNING_2:
28 if (true == EG_SomeRunningFunction2()) {
29 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
30 } else {
31 /* Something might go wrong, so transition to error state */
32 nextState = EG_FSM_STATE_ERROR;
33 }
34 break;
35
36 default:
37 FAS_ASSERT(FAS_TRAP);
38 break;
39 }
40 return nextState;
41 }
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
andthird substate
intoState C
andsecond substate
are made, but a strict chain needs to be followed:
State A
andthird substate
intoState C
andfirst substate
(EG_FSM_SUBSTATE_ENTRY
) intoState C
andsecond 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, whether
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"];
}](../../_images/graphviz-a5a47194eb9f8da99493cf16b7eb4aa35d8a7f6c.png)
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.
1 switch (pEgState->currentState) {
2 /* ... */
3 /****************************************************** STATE: ERROR */
4 case EG_FSM_STATE_ERROR:
5 nextState = EG_ProcessErrorState(pEgState);
6 if (nextState == EG_FSM_STATE_ERROR) {
7 /* staying in error state, processed by state function */
8 } else if (nextState == EG_FSM_STATE_UNINITIALIZED) {
9 EG_SetState(pEgState, EG_FSM_STATE_UNINITIALIZED, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
10 } else {
11 FAS_ASSERT(FAS_TRAP); /* Something went wrong */
12 }
13 break;
14 /* ... */
15 }
8.2.3.5. Full Example Code
The full implementation of this state machine is found in Listing 8.34 and Listing 8.35.
1/**
2 *
3 * @copyright © 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
4 * All rights reserved.
5 *
6 * SPDX-License-Identifier: BSD-3-Clause
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
13 *
14 * 2. Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution.
17 *
18 * 3. Neither the name of the copyright holder nor the names of its
19 * contributors may be used to endorse or promote products derived from
20 * this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 * We kindly request you to use one or more of the following phrases to refer to
34 * foxBMS in your hardware, software, documentation or advertising materials:
35 *
36 * - ″This product uses parts of foxBMS®″
37 * - ″This product includes parts of foxBMS®″
38 * - ″This product is derived from foxBMS®″
39 *
40 */
41
42/**
43 * @file state-machine.h
44 * @author foxBMS Team
45 * @date 2020-10-29 (date of creation)
46 * @updated 2020-11-09 (date of last update)
47 * @ingroup STATE_MACHINE
48 * @prefix EG
49 *
50 * @brief Header file of some software
51 *
52 */
53
54#ifndef FOXBMS__STATE_MACHINE_H_
55#define FOXBMS__STATE_MACHINE_H_
56
57/*========== Includes =======================================================*/
58#include "general.h"
59
60/*========== Macros and Definitions =========================================*/
61/** States of the state machine */
62typedef enum EG_FSM_STATES {
63 EG_FSM_STATE_DUMMY, /*!< dummy state - always the first state */
64 EG_FSM_STATE_HAS_NEVER_RUN, /*!< never run state - always the second state */
65 EG_FSM_STATE_UNINITIALIZED, /*!< uninitialized state */
66 EG_FSM_STATE_INITIALIZATION, /*!< initializing the state machine */
67 EG_FSM_STATE_RUNNING, /*!< operational mode of the state machine */
68 EG_FSM_STATE_ERROR, /*!< state for error processing */
69} EG_FSM_STATES_e;
70
71/** Substates of the state machine */
72typedef enum EG_FSM_SUBSTATES {
73 EG_FSM_SUBSTATE_DUMMY, /*!< dummy state - always the first substate */
74 EG_FSM_SUBSTATE_ENTRY, /*!< entry state - always the second substate */
75 EG_FSM_SUBSTATE_INITIALIZATION_0, /*!< fist initialization substate */
76 EG_FSM_SUBSTATE_INITIALIZATION_1, /*!< second initialization substate */
77 EG_FSM_SUBSTATE_INITIALIZATION_EXIT, /*!< last initialization substate */
78 EG_FSM_SUBSTATE_RUNNING_0, /*!< fist running substate */
79 EG_FSM_SUBSTATE_RUNNING_1, /*!< second running substate */
80 EG_FSM_SUBSTATE_RUNNING_2, /*!< third running substate */
81} EG_FSM_SUBSTATES_e;
82
83/** some struct with some information */
84typedef struct EG_INFORMATION {
85 uint8_t r0; /*!< some info 0 */
86 uint8_t r1; /*!< some info 0 */
87 uint8_t r2; /*!< some info 0 */
88} EG_INFORMATION_s;
89
90/** This struct describes the state of the monitoring instance */
91typedef struct EG_STATE {
92 uint16_t timer; /*!< timer of the state */
93 uint8_t triggerEntry; /*!< trigger entry of the state */
94 EG_FSM_STATES_e nextState; /*!< next state of the FSM */
95 EG_FSM_STATES_e currentState; /*!< current state of the FSM */
96 EG_FSM_STATES_e previousState; /*!< previous state of the FSM */
97 EG_FSM_SUBSTATES_e nextSubstate; /*!< next substate of the FSM */
98 EG_FSM_SUBSTATES_e currentSubstate; /*!< current substate of the FSM */
99 EG_FSM_SUBSTATES_e previousSubstate; /*!< previous substate of the FSM */
100 EG_INFORMATION_s information; /*!< Some information to be stored */
101} EG_STATE_s;
102
103/*========== Extern Constant and Variable Declarations ======================*/
104
105/** state of the example state machine */
106extern EG_STATE_s eg_state;
107
108/*========== Extern Function Prototypes =====================================*/
109/**
110 * @brief tick function, call this to advance the state machine
111 * @param pEgState current state of the state machine
112 * @returns returns always #STD_OK
113 */
114extern STD_RETURN_TYPE_e EG_Trigger(EG_STATE_s *pEgState);
115
116/*========== Externalized Static Functions Prototypes (Unit Test) ===========*/
117
118#endif /* FOXBMS__STATE_MACHINE_H_ */
1/**
2 *
3 * @copyright © 2010 - 2021, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
4 * All rights reserved.
5 *
6 * SPDX-License-Identifier: BSD-3-Clause
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
13 *
14 * 2. Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution.
17 *
18 * 3. Neither the name of the copyright holder nor the names of its
19 * contributors may be used to endorse or promote products derived from
20 * this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 *
33 * We kindly request you to use one or more of the following phrases to refer to
34 * foxBMS in your hardware, software, documentation or advertising materials:
35 *
36 * - ″This product uses parts of foxBMS®″
37 * - ″This product includes parts of foxBMS®″
38 * - ″This product is derived from foxBMS®″
39 *
40 */
41
42/**
43 * @file state-machine.c
44 * @author foxBMS Team
45 * @date 2020-10-29 (date of creation)
46 * @updated 2020-11-09 (date of last update)
47 * @ingroup STATE_MACHINE
48 * @prefix EG
49 *
50 * @brief Implementation of some driver that needs a state machine
51 *
52 */
53
54/*========== Includes =======================================================*/
55#include "state-machine.h"
56
57/*========== Macros and Definitions =========================================*/
58/**
59 * statemachine short time definition in #EG_Trigger calls until next state is
60 * processed
61 */
62#define EG_FSM_SHORT_TIME (1u)
63
64/**
65 * statemachine medium time definition in #EG_Trigger calls until next
66 * state/substate is processed
67 */
68#define EG_FSM_MEDIUM_TIME (5u)
69
70/**
71 * statemachine long time definition in #EG_Trigger calls until next
72 * state/substate is processed
73 */
74#define EG_FSM_LONG_TIME (10u)
75
76/** Symbolic names to check for multiple calls of #EG_Trigger */
77typedef enum EG_CHECK_MULTIPLE_CALLS {
78 EG_MULTIPLE_CALLS_NO, /*!< no multiple calls, OK */
79 EG_MULTIPLE_CALLS_YES, /*!< multiple calls, not OK */
80} EG_CHECK_MULTIPLE_CALLS_e;
81
82/*========== Static Constant and Variable Definitions =======================*/
83
84/*========== Extern Constant and Variable Definitions =======================*/
85
86/** local instance of the driver-state */
87EG_STATE_s eg_state = {
88 .timer = 0,
89 .triggerEntry = 0,
90 .nextState = EG_FSM_STATE_HAS_NEVER_RUN,
91 .currentState = EG_FSM_STATE_HAS_NEVER_RUN,
92 .previousState = EG_FSM_STATE_HAS_NEVER_RUN,
93 .nextSubstate = EG_FSM_SUBSTATE_DUMMY,
94 .currentSubstate = EG_FSM_SUBSTATE_DUMMY,
95 .previousSubstate = EG_FSM_SUBSTATE_DUMMY,
96 .information.r0 = 0,
97 .information.r1 = 0,
98 .information.r2 = 0,
99};
100
101/*========== Static Function Prototypes =====================================*/
102/**
103 * @brief check for multiple calls of state machine trigger function
104 * @details The trigger function is not reentrant, which means it cannot
105 * be called multiple times. This functions increments the
106 * triggerEntry counter once and must be called each time the
107 * trigger function is called. If triggerEntry is greater than
108 * one, there were multiple calls. For this function to work,
109 * triggerEntry must be decremented each time the trigger function
110 * is called, even if no processing do because the timer is
111 * non-zero.
112 * @param pEgState state of the fake state machine
113 * @return #EG_MULTIPLE_CALLS_YES if there were multiple calls,
114 * #EG_MULTIPLE_CALLS_NO otherwise
115 */
116static EG_CHECK_MULTIPLE_CALLS_e EG_CheckMultipleCalls(EG_STATE_s *pEgState);
117
118/**
119 * @brief Sets the next state, the next substate and the timer value
120 * of the state variable.
121 * @param pEgState state of the example state machine
122 * @param nextState state to be transferred into
123 * @param nextSubstate substate to be transferred into
124 * @param idleTime wait time for the state machine
125 */
126static void EG_SetState(
127 EG_STATE_s *pEgState,
128 EG_FSM_STATES_e nextState,
129 EG_FSM_SUBSTATES_e nextSubstate,
130 uint16_t idleTime);
131
132/**
133 * @brief Sets the next substate and the timer value
134 * of the state variable.
135 * @param pEgState state of the example state machine
136 * @param nextSubstate substate to be transferred into
137 * @param idleTime wait time for the state machine
138 */
139static void EG_SetSubstate(EG_STATE_s *pEgState, EG_FSM_SUBSTATES_e nextSubstate, uint16_t idleTime);
140
141/**
142 * @brief dummy function for initialization substate
143 * #EG_FSM_SUBSTATE_INITIALIZATION_0
144 * @return returns always true
145 */
146static bool EG_SomeInitializationFunction0(void);
147
148/**
149 * @brief dummy function for initialization substate
150 * #EG_FSM_SUBSTATE_INITIALIZATION_1
151 * @return returns always true
152 */
153static bool EG_SomeInitializationFunction1(void);
154
155/**
156 * @brief dummy function to check if the initialization
157 * step of the state machine was successful
158 * (#EG_FSM_SUBSTATE_INITIALIZATION_1)
159 * @return returns always true
160 */
161static bool EG_SomeInitializationFunctionExit(void);
162
163/**
164 * @brief dummy function making a test to determine
165 * the outcome of substate #EG_FSM_SUBSTATE_RUNNING_0
166 * @return returns always true
167 */
168static bool EG_SomeRunningFunction0(void);
169
170/**
171 * @brief dummy function making a test to determine
172 * the outcome of substate EG_FSM_SUBSTATE_RUNNING_1
173 * @return returns always true
174 */
175static bool EG_SomeRunningFunction1(void);
176
177/**
178 * @brief dummy function making a test to determine
179 * the outcome of substate EG_FSM_SUBSTATE_RUNNING_2
180 * @return returns always true
181 */
182static bool EG_SomeRunningFunction2(void);
183
184/**
185 * @brief Processes the initialization state
186 * @param pEgState state of the example state machine
187 * @return Always #STD_OK
188 */
189static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState);
190
191/**
192 * @brief Processes the running state
193 * @param pEgState state of the example state machine
194 * @return Always #STD_OK
195 */
196static EG_FSM_STATES_e EG_ProcessRunningState(EG_STATE_s *pEgState);
197
198/**
199 * @brief Defines the state transitions
200 * @details This function contains the implementation of the state
201 * machine, i.e., the sequence of states and substates.
202 * It is called by the trigger function every time
203 * the state machine timer has a non-zero value.
204 * @param pEgState state of the example state machine
205 * @return Always #STD_OK
206 */
207static STD_RETURN_TYPE_e EG_RunStateMachine(EG_STATE_s *pEgState);
208
209/*========== Static Function Implementations ================================*/
210
211static EG_CHECK_MULTIPLE_CALLS_e EG_CheckMultipleCalls(EG_STATE_s *pEgState) {
212 FAS_ASSERT(pEgState != NULL_PTR);
213 EG_CHECK_MULTIPLE_CALLS_e multipleCalls = EG_MULTIPLE_CALLS_NO;
214 OS_EnterTaskCritical();
215 if (pEgState->triggerEntry == 0u) {
216 pEgState->triggerEntry++;
217 } else {
218 multipleCalls = EG_MULTIPLE_CALLS_YES; /* multiple call of function EG_Trigger for instance pEgState */
219 }
220 OS_ExitTaskCritical();
221 return multipleCalls;
222}
223
224static void EG_SetState(
225 EG_STATE_s *pEgState,
226 EG_FSM_STATES_e nextState,
227 EG_FSM_SUBSTATES_e nextSubstate,
228 uint16_t idleTime) {
229 FAS_ASSERT(pEgState != NULL_PTR);
230 bool earlyExit = false;
231
232 pEgState->timer = idleTime;
233
234 if ((pEgState->currentState == nextState) && (pEgState->currentSubstate == nextSubstate)) {
235 /* Next state and next substate equal to current state and substate: nothing to do */
236 pEgState->nextState = EG_FSM_STATE_DUMMY; /* no state transistion required -> reset */
237 pEgState->nextSubstate = EG_FSM_SUBSTATE_DUMMY; /* no substate transistion required -> reset */
238 earlyExit = true;
239 }
240
241 if (earlyExit == false) {
242 if (pEgState->currentState != nextState) {
243 /* Next state is different: switch to it and set substate to entry value */
244 pEgState->previousState = pEgState->currentState;
245 pEgState->currentState = nextState;
246 pEgState->previousSubstate = pEgState->currentSubstate;
247 pEgState->currentSubstate = EG_FSM_SUBSTATE_ENTRY; /* Use entry state after a top level state change */
248 pEgState->nextState = EG_FSM_STATE_DUMMY; /* no state transistion required -> reset */
249 pEgState->nextSubstate = EG_FSM_SUBSTATE_DUMMY; /* no substate transistion required -> reset */
250 } else if (pEgState->currentSubstate != nextSubstate) {
251 /* Only the next substate is different, switch to it */
252 EG_SetSubstate(pEgState, nextSubstate, idleTime);
253 } else {
254 ;
255 }
256 }
257}
258
259static void EG_SetSubstate(EG_STATE_s *pEgState, EG_FSM_SUBSTATES_e nextSubstate, uint16_t idleTime) {
260 FAS_ASSERT(pEgState != NULL_PTR);
261 pEgState->timer = idleTime;
262 pEgState->previousSubstate = pEgState->currentSubstate;
263 pEgState->currentSubstate = nextSubstate;
264 pEgState->nextSubstate = EG_FSM_SUBSTATE_DUMMY; /* substate has been set, now reset value for nextSubstate */
265}
266
267static bool EG_SomeInitializationFunction0(void) {
268 return true;
269}
270
271static bool EG_SomeInitializationFunction1(void) {
272 return true;
273}
274
275static bool EG_SomeInitializationFunctionExit(void) {
276 return true;
277}
278
279static bool EG_SomeRunningFunction0(void) {
280 return true;
281}
282
283static bool EG_SomeRunningFunction1(void) {
284 return true;
285}
286
287static bool EG_SomeRunningFunction2(void) {
288 return true;
289}
290
291static EG_FSM_STATES_e EG_ProcessInitializationState(EG_STATE_s *pEgState) {
292 EG_FSM_STATES_e nextState = EG_FSM_STATE_INITIALIZATION; /* default behavior: stay in state */
293 switch (pEgState->currentSubstate) {
294 case EG_FSM_SUBSTATE_ENTRY:
295 /* Nothing to do, just transfer to next substate */
296 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_0, EG_FSM_SHORT_TIME);
297 break;
298
299 case EG_FSM_SUBSTATE_INITIALIZATION_0:
300 if (true == EG_SomeInitializationFunction0()) {
301 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_1, EG_FSM_SHORT_TIME);
302 } else {
303 /* Something went wrong, so transition to error state */
304 nextState = EG_FSM_STATE_ERROR;
305 }
306 break;
307
308 case EG_FSM_SUBSTATE_INITIALIZATION_1:
309 if (true == EG_SomeInitializationFunction1()) {
310 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_INITIALIZATION_EXIT, EG_FSM_SHORT_TIME);
311 } else {
312 /* Something went wrong, so transition to error state */
313 nextState = EG_FSM_STATE_ERROR;
314 }
315 break;
316
317 case EG_FSM_SUBSTATE_INITIALIZATION_EXIT:
318 if (true == EG_SomeInitializationFunctionExit()) {
319 /* Initialization was successful, so transition to running state */
320 nextState = EG_FSM_STATE_RUNNING;
321 } else {
322 /* Something went wrong, so transition to error state */
323 nextState = EG_FSM_STATE_ERROR;
324 }
325 break;
326
327 default:
328 FAS_ASSERT(FAS_TRAP);
329 break;
330 }
331 return nextState;
332}
333
334static EG_FSM_STATES_e EG_ProcessRunningState(EG_STATE_s *pEgState) {
335 EG_FSM_STATES_e nextState = EG_FSM_STATE_RUNNING; /* default behavior: stay in state */
336 switch (pEgState->currentSubstate) {
337 case EG_FSM_SUBSTATE_ENTRY:
338 /* Nothing to do, just transfer to next substate */
339 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
340 break;
341
342 case EG_FSM_SUBSTATE_RUNNING_0:
343 if (true == EG_SomeRunningFunction0()) {
344 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_1, EG_FSM_SHORT_TIME);
345 } else {
346 /* Something went wrong, so transition to error state */
347 nextState = EG_FSM_STATE_ERROR;
348 }
349 break;
350
351 case EG_FSM_SUBSTATE_RUNNING_1:
352 if (true == EG_SomeRunningFunction1()) {
353 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_2, EG_FSM_SHORT_TIME);
354 } else {
355 /* Something went wrong, so transition to error state */
356 nextState = EG_FSM_STATE_ERROR;
357 }
358 break;
359
360 case EG_FSM_SUBSTATE_RUNNING_2:
361 if (true == EG_SomeRunningFunction2()) {
362 EG_SetSubstate(pEgState, EG_FSM_SUBSTATE_RUNNING_0, EG_FSM_SHORT_TIME);
363 } else {
364 /* Something went wrong, so transition to error state */
365 nextState = EG_FSM_STATE_ERROR;
366 }
367 break;
368
369 default:
370 FAS_ASSERT(FAS_TRAP);
371 break;
372 }
373
374 return nextState;
375}
376
377static STD_RETURN_TYPE_e EG_RunStateMachine(EG_STATE_s *pEgState) {
378 STD_RETURN_TYPE_e ranStateMachine = STD_OK;
379 EG_FSM_STATES_e nextState = EG_FSM_STATE_DUMMY;
380 switch (pEgState->currentState) {
381 /********************************************** STATE: HAS NEVER RUN */
382 case EG_FSM_STATE_HAS_NEVER_RUN:
383 /* Nothing to do, just transfer */
384 EG_SetState(pEgState, EG_FSM_STATE_UNINITIALIZED, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
385 break;
386
387 /********************************************** STATE: UNINITIALIZED */
388 case EG_FSM_STATE_UNINITIALIZED:
389 /* Nothing to do, just transfer */
390 EG_SetState(pEgState, EG_FSM_STATE_INITIALIZATION, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
391 break;
392
393 /********************************************* STATE: INITIALIZATION */
394 case EG_FSM_STATE_INITIALIZATION:
395 nextState = EG_ProcessInitializationState(pEgState);
396 if (nextState == EG_FSM_STATE_INITIALIZATION) {
397 /* staying in state, processed by state function */
398 } else if (nextState == EG_FSM_STATE_ERROR) {
399 EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
400 } else if (nextState == EG_FSM_STATE_RUNNING) {
401 EG_SetState(pEgState, EG_FSM_STATE_RUNNING, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
402 } else {
403 FAS_ASSERT(FAS_TRAP); /* Something went wrong */
404 }
405 break;
406
407 /**************************************************** STATE: RUNNING */
408 case EG_FSM_STATE_RUNNING:
409 nextState = EG_ProcessRunningState(pEgState);
410 if (nextState == EG_FSM_STATE_RUNNING) {
411 /* staying in state, processed by state function */
412 } else if (nextState == EG_FSM_STATE_ERROR) {
413 EG_SetState(pEgState, EG_FSM_STATE_ERROR, EG_FSM_SUBSTATE_ENTRY, EG_FSM_SHORT_TIME);
414 } else {
415 FAS_ASSERT(FAS_TRAP); /* Something went wrong */
416 }
417 break;
418
419 /****************************************************** STATE: ERROR */
420 case EG_FSM_STATE_ERROR:
421 /* implement error processing here or trap */
422 break;
423
424 /**************************************************** STATE: DEFAULT */
425 default:
426 /* all cases must be processed, trap if unknown state arrives */
427 FAS_ASSERT(FAS_TRAP);
428 break;
429 }
430
431 return ranStateMachine;
432}
433
434/*========== Extern Function Implementations ================================*/
435extern STD_RETURN_TYPE_e EG_Trigger(EG_STATE_s *pEgState) {
436 FAS_ASSERT(pEgState != NULL_PTR);
437 bool earlyExit = false;
438 STD_RETURN_TYPE_e returnValue = STD_OK;
439
440 /* Check multiple calls of function */
441 if (EG_MULTIPLE_CALLS_YES == EG_CheckMultipleCalls(pEgState)) {
442 returnValue = STD_NOT_OK;
443 earlyExit = true;
444 }
445
446 if (earlyExit == false) {
447 if (pEgState->timer > 0u) {
448 if ((--pEgState->timer) > 0u) {
449 pEgState->triggerEntry--;
450 returnValue = STD_OK;
451 earlyExit = true;
452 }
453 }
454 }
455
456 if (earlyExit == false) {
457 EG_RunStateMachine(pEgState);
458 pEgState->triggerEntry--;
459 }
460 return returnValue;
461}
462
463/*========== Externalized Static Function Implementations (Unit Test) =======*/