# Adding a New State

CPF System States are derived from the `StateBase` class.

Full requirements for implementing a `StateBase` derivative can be found in `StateBase.hpp`. This document is meant as a general guide for adding new states and working within the event-driven model. New requirements may be added to the base class which aren't captured here. Also, the examples below are from the initial example and not updated with the system implementation.

**Table of Contents:**

1. [Defining a New State](#defining-a-new-state)
2. [Creating the New State](#creating-the-new-state)
3. [State Actions](#state-actions)
	1. [Adding Actions (Sub-states)](#adding-actions-substates)
	2. [Action Handler Implementation](#action-handler-implementation)
4. [Transitioning Between States and Actions](#transitioning-between-states-and-actions)
5. [Creating a State Object](#creating-a-state-object)

## Defining a New State

First, in `StateBase.hpp`, you will need to add your new system state to the `StateNames` enumeration _before `numStates`_. This enumeration ID will be associated with your state object.

```cpp
enum StateNames
{
	SMNotRunning = 0,
	firstState,
	secondState,
	// Insert a new state here, before numStates.
	numStates
};
```

In `StateBase.cpp`, you will need to add a name for your state to the `state_names` array. The names in this array must be kept in the same order as the `StateNames` enumeration.

```cpp
std::string_view state_names[StateNames::numStates] =
{
	"Not Running",
	"First State",
	"Second State"
	// Insert a new state name here
};
```

## Creating the New State

Next, create a folder to contain your system state. Inside of this folder, create `.hpp` and `.cpp` files for your state. In `CPF/State/CMakeLists.txt`, you will need to add these new files to the `target_sources(cpf_state)` call:

```cmake
target_sources(cpf_state PRIVATE
	StateBase.cpp
	StateBase.hpp
	StateVariables.cpp
	StateVariables.hpp
	FirstState/FirstState.cpp
	FirstState/FirstState.hpp
	SecondState/SecondState.cpp
	SecondState/SecondState.hpp
	
	# Add new state files here
)
```


In the header, declare a `class` for your state, inheriting from `StateBase`. You will need to define the required functions from `StateBase` (`doStateEntry()`, `doStateExit()` and `doStateActions()` as of when this tutorial was created). You will also need to create an enumeration that represents the "Actions" or sub-states for the new system state. You can declare any other methods and member variables that you require for your system's implementation as well.

Here is a sample state definition:

```cpp
#include "StateBase.hpp"
#include "StateVariables.hpp"

class FirstState final : public StateBase
{
public:
	enum Actions : uint8_t
	{
		getGPSTime,
		initLogger,
		numActions
	};

public:
	FirstState(uint32_t timeout = 0);

private:
	StateVariables SV;

	void doStateEntry() final;
	void doStateExit(bool timeout) final;
	StateNames doStateActions(const event_t& event) final;
};

```

In the `.cpp` file, you will need to supply implementations for the member functions. 

Any entry and exit actions for your system state should be performed in the `doStateEntry` and `doStateExit` implementations. The `StateBase` `enter` and `exit` functions will invoke these whenever a state transition is triggered. Most importantly, `doStateEntry` must perform an `ACTION_CHANGE()` to select the first action and generate an `EntryActionEvent()`.

```cpp
/// We do our initial action in doStateEntry(), which should be some action
/// that generates an event. The rest of the sequence happens in doStateActions in
/// response to the events.
void FirstState::doStateEntry()
{
	ACTION_CHANGE(current_action_, getGPSTime);
}
```

We recommend implementing the constructor in the `.cpp` file, as this will allow you to keep the details of the action class implementations hidden from users of the header.

## State Actions

Actions are sub-states of the top-level system state.

Each action will need an associated enumeration value defined within the state class.

```cpp
enum Actions : uint8_t
{
	getGPSTime,
	initLogger,
	numActions
};
```

The state class also needs to track the current action in a variable:

```cpp
private:
  Actions current_action_;
```

The main work of a state class is done in `doStateActions()`. Essentially, this function performs a `switch/case` operation to forward the incoming message to the appropriate action handler. Of course, handler functions aren't required - if an action is simple enough, you could also handle a message directly in the appropriate case.

```cpp
StateNames SecondState::doStateActions(const event_t& event)
{
	StateNames next_state;

	switch(current_action_)
	{
	case getGPSPosition:
		next_state = getGPSPositionActionHandler(event);
		break;
	case suspendTheSystem:
		next_state = suspendTheSystemActionHandler(event);
		break;
	case deepSleepTheSystem:
		next_state = deepSleepTheSystemActionHandler(event);
		break;
	default:
		assert(0); // Unexpected case!
	}

	return next_state;
}
```

### Adding Actions (Sub-states)

When adding a new action to a state, you need to:

1. Add a new value to the Actions enumeration
2. Define a new function (as a private member function or a standalone function) to handle the operation.
	1. The prototype must match: `StateNames actionHandlerName(const event_t& event)`
2. Modify `doStateActions` to add the new Action enumeration value and forward the input event to the new handler function.

### Action Handler Implementation

An action handler is a function with the prototype `StateNames actionHandlerName(const event_t& event)`. 

The main body of the function will be a `switch/case` statement that checks the value of the incoming event's message ID (`event.get_message_id()`).

At a minimum, action handlers need two cases:

1. `case ID_EntryAction`, which defines the actions that should be taken when entering a new action.
	+ This will typically set up work that will generate events: 
		* starting a timer to trigger periodic event generation
		* Requesting a new GPS sample
	+ If there is only simple work to be performed (e.g., initializing the logger, putting the system into deepSleep), it may be that _all_ work is done within the entry handler.
2. `default`, which handles unexpected event IDs.
	+ For most cases, you can use the `DEFAULT_UNKNOWN_EVENT_HANDLER()` macro (defined in `StateBase.hpp`) to use the default handler implementation. However, you can re-implement this function if different behavior is required.
	
In addition, you will need to implement a case for each event type that your action should handle.

If the event does not have any data, you can simply implement the action that should be taken in response to the event:

```cpp
	case ID_GPSNewPositionReady:
	{
		// New position ready, let's get it
		auto fix = gps_.getLatestPosition();

		// ... do something with the GPS position
		break;
	}
```

If the event contains data, you need to cast to the appropriate type. There is a helper macro, `CONVERT_TO_EVENT_TYPE()`, to make this easier (defined in `StateBase.hpp`).

```cpp
// Type here is: const GPSCommunicationErrorEvent*
auto communication_error_event = CONVERT_TO_EVENT_TYPE(event, GPSCommunicationErrorEvent);
```

Once you've accessed the appropriate type, you can make use of the data.

```cpp
	case ID_GPSCommunicationError:
	{
		auto communication_error_event = CONVERT_TO_EVENT_TYPE(event, GPSCommunicationErrorEvent);
		
		std::cout << "[Action: getGPSPosition] GPSCommunicationErrorEvent: ";
		switch(communication_error_event->error)
		{
			case GPSCommunicationErrorEvent::Error::ErrorUnknown:
				std::cout << "ErrorUnknown";
				break;
			case GPSCommunicationErrorEvent::Error::UnexpectedMessage:
				std::cout << "UnexpectedMessage";
				break;
			case GPSCommunicationErrorEvent::Error::Timeout:
				std::cout << "Timeout";
				break;
			case GPSCommunicationErrorEvent::Error::BusError:
				std::cout << "BusError";
				break;
		}
		std::cout << ". Going to request again." << std::endl;

		// An error was received - so we're going to try gettign the timestamp again, until some other
		// logic is added.
		// TODO: probably want to check error and take an action here (count error before trying again)
		gps_.requestPosition();
		break;
	}
```

## Transitioning Between States and Actions

**All Action and State transitions are triggered from within an Action handler**. 

- If no state or action change is required, return the current system state. (e.g., `return state_id_`)
- System state changes are triggered by having an action handler function return the desired next state.
- Action changes (sub-state changes within a system state) should be handled through the `ACTION_CHANGE()` macro defined in `StateBase.hpp`, while returning the same system state.
	+ + It is important to invoke this macro to ensure that an `EntryActionEvent` is properly created and enqueued.

## Creating a State Object

At this point, pending any other implementation details, your state is ready for use. The final step occurs in `CPFStateProcessing.cpp`. You will need to create an instance for your new state in `initStateProcessing()`:

```cpp
void initStateProcessing()
{
	std::cout << "[StateLoop] Initializing state processing system" << std::endl;
	stateArray[SMNotRunning] = new SecondState(UINT32_MAX);
	stateArray[firstState] = new FirstState(UINT32_MAX);
	stateArray[secondState] = new SecondState(UINT32_MAX);
	// Insert a new stateArray assignment here, with your class
{}
```
