# 7. Treat CPF State Actions as ETL FSM states

Date: 2023-02-13

## Status

Rejected in favor of a manual processing/casting implementation (ADR 0010). This was closer to the old implementation and will be easier to work with.

## Context

- State processing code must change to work with the event system.
- The CPF state processing code works with concepts of "System States" and "Actions".
	+ Essentially, the system is operating as a hierarchical state machine (HSM), although it is using alternate terminology.
	+ Actions are effectively sub-states of each system state. 
		* The state repeatedly runs a specific action until a set of conditions are true, and then it moves on to the next action.
- State-based code is currently scattered throughout the system, rather than being contained within the state machine region.
	+ For example, `gps.getFix` contains logic for multiple cases. The action repeatedly re-runs the same function call until the expected return value is received.
		* Generating a request for a new fix (returning a specific value indicating the operation did not complete)
		* Parsing a received fix and returning it (returning a specific value to indicate the operation did complete)

## Decision

We will not opt for a full-on HSM model, but will preserve the existing terminology and general form. However, we will treat each system state as its own FSM, and Actions as FSM states.

- StateBase will inherit from `etl::fsm`
- StateBase will have a `process()` function that takes an event input and passes it to the state machine's `receive()` function
- Actions for each state will be defined as `etl::fsm_state`

## Consequences

- Many components in the system will become much simpler.
	+ For example, rather than handling different cases in the GPS driver, it can instead provide much simpler implementations:
		* `gps.requestFix()`, which sets up the UART operation. An event will be generated when a new fix is received.
		* `gps.getLatestFix()`, which returns the latest value.
- State code becomes more complex. 
	+ Before, a simple switch/case was used. Now, actions need to be defined as classes, and handlers need to be provided for expected events.
- State-dependent logic will be kept in one place, rather than being spread throughout the system. This will also result in apparent complexity increase in the State code, although this is mostly an illusion: the logic is being moved from one region to another.

## Alternatives Considered

- [HSM Model](#hsm-model)
- [Manual Message Processing/Casting](#manual-message-processing-casting)

### HSM Model

We could treat the full system as a hierarchical FSM, using the "HSFM" support provided by the ETL. This would be a more appropriate model of the overall system, as it is indeed a HFSM.

This would simplify much of the state processing code, as transitions and entry/exit actions are handled by the framework. Essentially, you would just need a simplified event pump. CPFStateProcesing logic and the StateBase will largely go away.

```cpp
void CentralEventQueue::process()
{
	while events
		auto msg = q.front();
		// This example relies on chaining:
		// Default router handles callables
		// HSFM is added as a follow-on processor for all other events
		send_message(router, msg);
		q.pop();
}

void systemLoop()
{
	while not_forced_exit:
		CentralEventQueue::process()
		sleep();
}
```

Problematically, there is no differentiation between top-level states and their sub-states from the perspective of defining states. This would result in a single state `enum`, such as:

```cpp
enum StateNames {
	InitMission
	InitMission_Action1
	InitMission_Action2
	InitProfile
	InitProfile_Action1
	InitProfile_Action2
}
```

Initializing the system would have its own level of complexity: you have to build the initial FSM, and attach arrays of child states to appropriate points.

However, this does come with a solution: there is no hackery necessary when transitioning between one action and a new top-level state, like is present in the proposed model.

### Manual Message Processing/Casting

We could make it easy to process events while working in the “old way” (a function with switch/case based on the current action, rather than treating actions as states). This has the benefit of being the closest to the previous incarnations of the system.

Essentially, the redesigned StateBase `process` function will still take an `etl::imessage&` input. This will then be forwarded to the derived class's template method that handles the event (e.g., `doActions(event)`).

The derived class can handle the event however it wants, such as in a `switch/case` or in an array of function pointers accessed via the current action index. It will still require a refinement in how actions are thought about (e.g., more action states will need to be defined as part of the relocation of state logic into the state system).

For events without data, you can identify what the event type is by checking the ID (`event.get_message_id()`).

Where it gets more complicated, however, is events with data. You must check the ID, and then cast the event to the appropriate type. This is done automatically (and safely) by the ETL FSM/HSFM code, but would need to be manually done in this solution. Making it manual also presents risk of introducing errors, e.g. casting to the incorrect type.


