This chapter should be read in conjunction with
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 eventE occurs, we should perform the actionsA and make a transition to the stateS' .
For an FSM implemented using the
StateName(Event, StateData) -> .. code for actions here ... {next_state, StateName', StateData'}
A door with a code lock could be viewed as an FSM. Initially, the door is locked. Anytime someone presses a button, this generates an event. Depending on what buttons have been pressed before, the sequence so far may be correct, incomplete or wrong.
If it is correct, the door is unlocked for 30 seconds (30000 ms). If it is incomplete, we wait for another button to be pressed. If it is is wrong, we start all over, waiting for a new button sequence.
Implementing the code lock FSM using
gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code, []).
button(Digit) ->
gen_fsm:send_event(code_lock, {button, Digit}).
init(Code) ->
{ok, locked, {[], Code}}.
locked({button, Digit}, {SoFar, Code}) ->
case [Digit|SoFar] of
Code ->
do_unlock(),
{next_state, open, {[], Code}, 30000};
Incomplete when length(Incomplete)
{next_state, locked, {Incomplete, Code}};
_Wrong ->
{next_state, locked, {[], Code}}
end.
open(timeout, State) ->
do_lock(),
{next_state, locked, State}.]]>
The code is explained in the next sections.
In the example in the previous section, the gen_fsm is started by
calling
start_link(Code) ->
gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).
The first argument
If the name is omitted, the gen_fsm is not registered.
Instead its pid must be used. The name could also be given as
The second argument,
In this case, the interface functions (
The third argument,
The fourth argument, [], is a list of options. See
If name registration succeeds, the new gen_fsm process calls
the callback function
init(Code) ->
{ok, locked, {[], Code}}.
Note that
The function notifying the code lock about a button event is
implemented using
button(Digit) ->
gen_fsm:send_event(code_lock, {button, Digit}).
The event is made into a message and sent to the gen_fsm. When
the event is received, the gen_fsm calls
case [Digit|SoFar] of
Code ->
do_unlock(),
{next_state, open, {[], Code}, 30000};
Incomplete when length(Incomplete)
{next_state, locked, {Incomplete, Code}};
_Wrong ->
{next_state, locked, {[], Code}};
end.
open(timeout, State) ->
do_lock(),
{next_state, locked, State}.]]>
If the door is locked 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 unlocked
and the gen_fsm goes to state
When a correct code has been given, the door is unlocked and
the following tuple is returned from
{next_state, open, {[], Code}, 30000};
30000 is a timeout value in milliseconds. After 30000 ms, i.e.
30 seconds, a timeout occurs. Then
open(timeout, State) ->
do_lock(),
{next_state, locked, State}.
Sometimes an event can arrive at any state of the gen_fsm.
Instead of sending the message with
-module(code_lock).
...
-export([stop/0]).
...
stop() ->
gen_fsm:send_all_state_event(code_lock, stop).
...
handle_event(stop, _StateName, StateData) ->
{stop, normal, StateData}.
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. Exactly how this is done is defined by a
If it is necessary to clean up before termination, the shutdown
strategy must be a timeout value and the gen_fsm must be set to
trap exit signals in the
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, for example:
...
-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.
The callback function handling the
If the gen_fsm should be able to receive other messages than
events, the callback function
handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
..code to handle exits here..
{next_state, StateName1, StateData1}.
The code_change method also has to be implemented.
code_change(OldVsn, StateName, StateData, Extra) ->
..code to convert state (and more) during code change
{ok, NextStateName, NewStateData}