> ## 3 Gen_Fsm Behaviour

> This chapter should be read in conjunction with `gen_fsm(3)`, where all interface functions and callback functions are described in detail.

> ### 3.1 Finite State Machines

> A finite state machine, FSM, can be described as a set of relations of the form:

>     State(S) x Event(E) -> Actions(A), State(S')

> These relations are interpreted as meaning:

> > If we are in state `S` and the event `E` occurs, we should perform the actions `A` and make a transition to the state `S'`.

> For an FSM implemented using the `gen_fsm` behaviour, the state transition rules are written as a number of Erlang functions which conform to the following convention:

>     StateName(Event, StateData) ->
>         .. code for actions here ...
>         {next_state, StateName', StateData'}

> ### 3.2 Example

> A door with a code lock could be viewed as an FSM. Initially, the door is closed. When someone presses a button, this generates an event. If the button sequence pressed so far is the correct code, the door is opened and held open for 30 seconds (30000 milliseconds). If the button sequence is incomplete, the door remains closed and we wait for another button to be pressed. If the button sequence is wrong, we start all over, waiting for a new button sequence.

> Implementing the code lock FSM using `gen_fsm` results in this callback module:

>     -module(code_lock).
>     -behaviour(gen_fsm).

>     -export([start_link/1]).
>     -export([button/1]).
>     -export([init/1, closed/2, open/2]).

>     start_link(Code) ->
>         gen_fsm:start_link({local, code_lock}, code_lock, Code, []).

>     button(Digit) ->
>         gen_fsm:send_event(code_lock, {button, Digit}).

>     init(Code) ->
>         {ok, closed, {[], Code}}.

>     closed({button, Digit}, {SoFar, Code}) ->
>         case [Digit|SoFar] of
>             Code ->
>                 do_open(),
>                 {next_state, open, {[], Code}, 3000};
>             Incomplete when length(Incomplete)<length(Code) ->
>                 {next_state, closed, {Incomplete, Code}};
>             _Wrong ->
>                 {next_state, closed, {[], Code}};
>         end.

>     open(timeout, State) ->
>         do_close(),
>         {next_state, closed, State}.

> The code is explained in the next sections.

> ### 3.3 Starting a Gen_Fsm

> In the example in the previous section, the gen_fsm is started by calling `code_lock:start_link(Code)`:

>     start_link(Code) ->
>         gen_fsm:start_link({local, code_lock}, code_lock, Code, []).

> `start_link` calls the function `gen_fsm:start_link/4`. This function spawns and links to a new process, a gen_fsm.

> - The first argument `{local, code_lock}` specifies the name. In this case, the gen_fsm will be locally registered as `code_lock`.
>   If the name is omitted, the gen_fsm is not registered. Instead its pid must be used. The name could also be given as `{global, Name}`, in which case the gen_fsm is registered using `global:register_name/2`.
> - The second argument, `code_lock`, is the name of the callback module, that is the module where the callback functions are located.
>   In this case, the interface functions (`start_link` and `button`) are located in the same module as the callback functions (`init`, `closed` and `open`). This is normally good programming practice, to have the code corresponding to one process contained in one module.
> - The third argument, `Code`, is a term which is passed as-is to the callback function `init`. Here, `init` gets the correct code for the lock as indata.
> - The fourth argument, [], is a list of options. See `gen_fsm(3)` for available options.

> If name registration succeeds, the new gen_fsm process calls the callback function `code_lock:init(Code)`. `init` is expected to return `{ok, StateName, StateData}`, where `StateName` is the name of the initial state of the gen_fsm. In this case `closed`, assuming the door is closed to begin with. `StateData` is the internal state of the gen_fsm. (For gen_fsms, the internal state is often referred to 'state data' to distinguish it from the state as in states of a state machine.) In this case, the state data is the button sequence so far (empty to begin with) and the correct code of the lock.

>     init(Code) ->
>         {ok, closed, {[], Code}}.

> Note that `gen_fsm:start_link` is synchronous. It does not return until the gen_fsm has been initialized and is ready to receive notifications.

> ### 3.4 Notifying About Events

> The function notifying the code lock about a button event is implemented using `gen_fsm:send_event/2`:

>     button(Digit) ->
>         gen_fsm:send_event(code_lock, {button, Digit}).

> `code_lock` is the name of the gen_fsm and must agree with the name used to start it. `{button, Digit}` is the actual event.

> The event is made into a message and sent to the gen_fsm. When the event is received, the gen_fsm calls `StateName(Event, StateData)` which is expected to return a tuple `{next_state, StateName1, StateData1}`. `StateName` is the name of the current state and `StateName1` is the name of the next state to go to. `StateData1` is a new value for the state data of the gen_fsm.

>     closed({button, Digit}, {SoFar, Code}) ->
>         case [Digit|SoFar] of
>             Code ->
>                 do_open(),
>                 {next_state, open, {[], Code}, 30000};
>             Incomplete when length(Incomplete)<length(Code) ->
>                 {next_state, closed, {Incomplete, Code}};
>             _Wrong ->
>                 {next_state, closed, {[], Code}};
>         end.

>     open(timeout, State) ->
>         do_close(),
>         {next_state, closed, State}.

> If the door is closed and a button is pressed, the complete button sequence so far is compared with the correct code for the lock and, depending on the result, the door is either opened and the gen_fsm goes to state `open`, or the door remains in state `closed`.

> ### 3.5 Timeouts

> When a correct code has been givened, the door is opened and the following tuple is returned from `closed/2`:

>     {next_state, open, {[], Code}, 30000};

> 30000 is a timeout value in milliseconds. After 30000 milliseconds, i.e. 30 seconds, a timeout occurs. Then `StateName(timeout, StateData)` is called. In this case, the timeout occurs when the door has been in state `open` for 30 seconds and then the door is closed:

>     open(timeout, State) ->
>         do_close(),
>         {next_state, closed, State}.

> ### 3.6 All State Events

> Sometimes an event can arrive at any state of the gen_fsm. Instead of sending the message with `gen_fsm:send_event/2` and writing one clause handling the event for each state function, the message can be sent with `gen_fsm:send_all_state_event/2` and handled with `Module:handle_event/3`:

>     -module(code_lock).
>     ...
>     -export([stop/0]).
>     ...

>     stop() ->
>         gen_fsm:send_all_state_event(code_lock, stop).

>     ...

>     handle_event(stop, _StateName, StateData) ->
>         {stop, normal, StateData}.

> ### 3.7 Stopping

> If the gen_fsm is part of a supervision tree, no stop function is needed. The gen_fsm will automatically be terminated by its supervisor calling `exit(Pid, shutdown)`.

> The gen_fsm process does not trap exit signals. If it is necessary to do some cleaning up before termination, it should be set to trap exit signals in the `init` function and another callback function `terminate(Reason, State)` should be implemented doing the cleaning up:

>     init(Args) ->
>         ...,
>         process_flag(trap_exit, true),
>         ...,
>         {ok, StateName, StateData}.

>     ...

>     terminate(shutdown, StateName, StateData) ->
>         ..code for cleaning up here..
>         ok.

> If the gen_fsm is not part of a supervision tree, a stop function may be useful. The gen_fsm will automatically call the callback function `terminate(Reason, StateName, StateData)` if another of the callback functions returns `{stop, Reason, State2}` instead of `{next_state,...}`. Example:

>     -module(code_lock).
>     ...
>     -export([stop/0]).
>     ...

>     stop() ->
>         gen_fsm:send_all_state_event(code_lock, stop).

>     ...

>     handle_event(stop, _StateName, StateData) ->
>         {stop, normal, StateData}.

>     ...

>     terminate(normal, _StateName, _StateData) ->
>         ok.

> ### 3.8 Handling Other Messages

> If the gen_fsm should be able to receive other messages than events, the callback function `handle_info(Info, StateName, StateData)` must be implemented to handle them. Examples on other messages could be exit messages, if the gen_fsm is linked to other processes (than the supervisor) and trapping exit signals.

>     handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
>         ..code to handle exits here..
>         {next_state, StateName1, StateData1}.

Copyright © 1991-2003 Ericsson Utvecklings AB