Skip to content

State Machines

1. Introduction

A state machine is a software subsystem whose behavior is described by states and transitions, together with related concepts such as signals, actions, and guards. State machines are important in flight software and embedded programming. In this section, we document the features of F Prime that support programming with state machines.

2. External and Internal State Machines

F Prime supports two kinds of state machines: external state machines and internal state machines. An external state machine is specified by an external tool, typically the State Autocoding for Real-Time Systems (STARS) tool. An internal state machine is specified in FPP, the modeling language for F Prime.

To program with external state machines, you typically do the following:

  1. Use an external tool, such as the Quantum Modeler or Plant UML, to express the state machine.
  2. Use the STARS autocoder to generate an implementation from the model.
  3. Write a small amount of code to make the FPP model aware of the implementation.
  4. Instantiate the state machine in one or more F Prime components.
  5. In the component implementations, write code that interacts with the state machine instances.

Steps 1, 2, and 5 are described in the STARS documentation. Steps 3 and 4 are described in the The FPP User's Guide. In the rest of this document, we will focus on the design of internal state machines.

3. FPP Modeling and Code Generation

To work with internal state machines in FPP, you do the following:

  1. Define one or more state machines, specifying their behavior.

  2. Add one or more instances of the state machines defined in step 1 to a component C.

  3. In the implementation of C, write code that interacts with the generated code for the instances defined in step 2.

Steps 1 and 2 are fully documented in The FPP User's Guide. Here we focus on the generated code for state machines and for state machine instances.

4. State Machine Definitions

In this section we describe the generated code for a state machine definition D with name M. This code is generated into files MStateMachineAc.hpp and MStateMachineAc.cpp when you run fpp-to-cpp on an FPP model that includes D.

In the ordinary way of programming with F Prime, it is unlikely that you will directly interact with the code described here. Instead, you will use the component interface to state machine instances described in the next section. Therefore, if your primary interest is to program with F Prime state machines, you can skip this section.

For examples of generated code, you can do the following:

  1. In a local installation of the fprime repository, go into FppTest and run fprime-util generate --ut and then fprime-util check.

  2. Look at the FPP models in fprime/FppTest/state_machine/internal/state.

  3. Look at the generated files in fprime/FppTest/build-fprime-automatic-native-ut/FppTest/state_machine/internal/state.

4.1. The State Machine Base Class

Each state machine definition D in the FPP model becomes a C++ base class MStateMachineBase, where M is the unqualified name of the definition. This class is enclosed in the namespaces, if any, given by the qualified name of D. For example, a state machine definition whose qualified name is A.B.M in FPP becomes a class A::B::MStateMachineBase in C++. The base class provides a partial implementation which is completed when the state machine is instantiated.

4.2. The Public Interface

Each generated state machine has the following public interface.

Types:

  • There is an enumeration representing the states of the state machine. These are the leaf states specified in the FPP model together with a special uninitialized state.

  • There is an enumeration representing the signals of the state machine. These are the signals specified in the FPP model together with a special signal that represents the initial transition on startup.

Member functions:

  • There is a function getState for getting the current state of the state machine.

  • There is one function sendSignal s for each signal s specified in the FPP model. If the signal s carries a value of type T, then this function has one formal parameter of type paramType(T); otherwise it has no formal parameters. Here paramType(T) means (1) T if T is a primitive type; otherwise (2) const reference to Fw::StringBase if T is a string type; otherwise (3) const reference to T.

4.3. The Protected Interface

Constructors and destructors: There is a zero-argument constructor and a destructor.

Initialization: There is a function initBase with a single formal parameter id of type FwEnumStoreType. This function must be called on a state machine instance before any signals are sent to the instance. The parameter id represents the state machine identifier. The type is FwEnumStoreType because the state machine identifier type is an enumeration defined in the subclass.

Actions: There is one pure virtual function action_a for each action a specified in the FPP model. Each action returns void has a formal parameter signal of type Signal. If the action has a type T, then there is a second formal parameter of type paramType(T).

Guards: There is one pure virtual const function guard_g for each guard g specified in the FPP model. Each guard returns bool and has a formal parameter signal of type Signal. If the guard has a type T, then there is a second formal parameter of type paramType(T).

Member variables: Each state machine base class has the following member variables:

  • A member m_id of type FwEnumStoreType. This variable records the current state of the state machine, represented as FwEnumStoreType. The initial value is zero.

  • A member m_state of type State. This variable records the current state of the state machine. The initial value is State::__FPRIME_AC_UNINITIALIZED.

4.4. The Private Interface

For each state S and choice C in the state machine there is one entry function for S or C. This function implements the entry behavior for S or C as specified in The FPP Language Specification.

5. State Machine Instances

In this section we describe the generated code for instances of state machines that are part of a component C. This code is part of the auto-generated base class for C. In general there may be any number of instances of any number of state machines.

For examples of generated code, you can do the following:

  1. In a local installation of the fprime repository, go into FppTest and run fprime-util generate --ut and then fprime-util check.

  2. Look at the FPP models in fprime/FppTest/state_machine/internal_instance/state.

  3. Look at the generated files in fprime/FppTest/build-fprime-automatic-native-ut/FppTest/state_machine/internal_instance/state.

5.1. State Machine Identifiers

There is an enumeration SmId with numeric type FwEnumStoreType that represents the state machine identifiers. There is one enumerated constant for each state machine instance in C.

5.2. State Machine Implementation Classes

There is one implementation class for each state machine definition M that is the type of a state machine instance in C. For example, if a state machine instance

state machine instance m: M
appears in the definition of C, then the auto-generated base class for C contains an implementation class for M. This class has the following properties:

  • It is a protected inner class of the auto-generated base class for C.

  • Its name is the fully qualified name of the state machine, converted to a C++ identifier by replacing the dots with underscores. For example, if a state machine has name A.B.M in FPP, the C++ name of its implementation class is A_B_M. We will refer to this name as fqCppIdent(M).

  • It is a public derived class of the state machine base class for M.

The following class diagram shows these properties, for a state machine A.B.M instantiated in a component C:

State Machine Implementation Class Diagram

Each state machine implementation class has the following elements in its interface.

Member variables: There is a member m_component that is a reference to the enclosing component instance. This way the state machine instance can call into the interface of the component instance.

Construction: There is a public constructor that takes a reference *this to the enclosing component as an argument. It initializes the member variable described above.

Initialization: There is a public function init with one formal parameter smId of type SmId. This function casts its argument to FwEnumStoreType and calls the function initBase defined in the base class. Thus it provides a type-safe way to initialize the state machine ID.

State ID: There is a public function getId that returns the state machine ID. It gets the value out of the m_id field defined in the base class and casts it to SmId. Thus it provides a type-safe way to get the state machine ID.

Actions: For each action a of M, there is one private function that implements the pure virtual function for a defined in the base class for M. The implementation calls the pure virtual function in the bass class for C that corresponds to to M and a. It passes in the state machine ID of *this.

Guards: For each guard g of M, there is one private function that implements the pure virtual function for g defined in the base class for M. The implementation calls the pure virtual function in the base class for C that corresponds to to M and g. It passes in the state machine ID of *this and returns the Boolean value returned by that function.

5.3. State Machine Instance Variables

For each state machine m in the FPP component model, there is one private member variable m_stateMachine_m. Its type is the state machine implementation class corresponding to the state machine M instantiated by m.

5.4. State Machine Initialization

When a component C instantiates one or more state machines, the standard init function of C calls the init function on each state machine instance, passing in the enumerated constant for each state machine ID. In the standard sequence for F Prime FSW initialization, the init function is called before any component instances are connected. Therefore, the initial transition of any state machine, including any entry actions of the initial state, may not emit events or telemetry or invoke any output port. If you need to emit events or telemetry or invoke an output port at the start of steady-state execution, you can have the initial state be a state INIT that does nothing but transition to another state START on an RTI signal. When the component instance receives an RTI call on its schedIn port, it can send the RTI signal to the state machine, causing the transition to START. At this point the components are connected, and the transition to START can emit events or telemetry or invoke an output port.

5.5. Protected Member Functions

The auto-generated base class for C has the following protected functions.

5.5.1. Implemented Functions

The following functions have complete implementations. They are available to call in the derived class that implements C.

State getter functions: For each state machine instance m in C, there is a const function m_getState that gets the current state of m.

Signal send functions: For each state machine instance m, and for each signal s defined in the state machine M instantiated by m, there is a function m_sendSignal_s for sending s to m. If s carries data of type T, then this function has a single formal parameter of type paramType(T); otherwise it has no formal parameters.

Calling a signal send function puts a message on the queue of the current instance of the component C. When the message is dispatched, the auto-generated code calls the function that sends the signal to the state machine. This way state machines can safely send signals when they are doing actions.

Each send signal function does the following:

  1. Call sendSignalStart to begin constructing a message buffer B.

  2. If the signal carries data, then serialize the data into B.

  3. Call the appropriate sendSignalFinish function to put B onto the component queue and handle overflow.

5.5.2. Pure Virtual Functions

The following functions are pure virtual in the generated base class for C. You must implement them in the derived class that implements C. When you generate a C++ component implementation template for C, you get a stub for each of these functions that you can fill in.

Action functions: For each state machine M instantiated in C, for each action a specified in M, there is a pure virtual function fqCppIdent(M)_action_a. Recall that fqCppIdent(M) is the fully qualified name of M represented as a C++ identifier, i.e., the fully qualified name of M in FPP with the dots replaced by underscores. This function has at least two formal parameters: the state machine ID and the signal. If the action requires a value of type T, then there is a third formal parameter of type paramType(T).

When an instance m of M does action a, it calls the action function for a in the auto-generated base class of M. That function calls fqCppIdent(M)_action_a with the correct state machine ID, signal, and value, if any.

Guard functions: For each state machine M is instantiated in C, for each guard g specified in M, there is a pure virtual function fqCppIdent(M)_guard_g. This is a const function that returns bool. It has the same formal parameters as an action function that requires the same value type, if any.

When an instance m of M evaluates guard g, it calls the guard function for g in the auto-generated base class of M. That function calls fqCppIdent(M)_guard_g with the correct state machine ID, signal, and value, if any, and returns the resulting Boolean value.

Overflow hook functions: For each state machine instance m that has overflow behavior hook, there is a pure virtual function m_stateMachineOverflowHook with the following formal parameters: the state machine ID, the signal, and a reference to the message buffer. The deserialization pointer of the message buffer points to the start of the message data, so the data can be deserialized if needed. If the signal that caused the overflow carries no data, then the deserialization pointer is at the end of the buffer, and deserializing data from the buffer will return a BUFFER_EMPTY error.

5.6. Private Member Functions

The generated base class for C has the following private member functions.

Send signal helper functions:

  • A function sendSignalStart. Each send signal function calls this function to begin constructing a message buffer for sending a state machine signal. This function serializes the following data into the message buffer: message type, the port number, the state machine ID, and the signal.

  • For each state machine instance m, a function m_sendSignalFinish. This function puts the message buffer on the queue with the correct priority and overflow behavior for m.

Helper functions for state machine dispatch:

  • A function smDispatch for initial dispatch of a state machine signal message from the queue. This function does the following:

  • Call deserializeSmIdAndSignal to deserialize the state machine ID and signal from the message buffer as FwEnumStoreType values.

  • Cast the state machine ID to SmId and use it to select the target state machine instance m.

  • Cast the signal to the appropriate type for m.

  • Call the appropriate smDispatch helper for the state machine M of which m is an instance, passing the message buffer, a reference to m_stateMachine_m, and the signal.

  • A function deserializeSmIdAndSignal for deserializing the state machine ID and signal from the message buffer as FwEnumStoreType values.

  • For each state machine M that is instantiated in C, a function fqCppIdent(M)_smDispatch for finishing the dispatch of a signal to a state machine instance of type M. It takes as arguments the message buffer B, a reference sm to a state machine instance, and a signal s. It deserializes the data from B if there is any for s. Then it calls sm.sendSignal_s, passing in the data, if any.